# Subject link

> A link to any subject type, titled and routed by subject_titles, with plain text for a record the caller can't read.

Source: https://bettersupabase.com/docs/registry/ui-recipes/subject-link

{/* Generated by scripts/build-recipes.ts from the example apps. Edit the examples or the script, then run `pnpm recipes`. */}

It calls `useSubject` from `better-supabase/blocks/subjects/react`; the [subjects block](https://bettersupabase.com/blocks/subjects) must be installed (`npx better-supabase add subjects`). Its page on bettersupabase.com is [/recipes/subject-link](https://bettersupabase.com/recipes/subject-link).

Web, with shadcn components:

```bash
npx shadcn add button skeleton
```

```tsx title="apps/examples/nextjs/src/features/subjects/components/subject-link.tsx"
"use client";

import type { SubjectRef } from "better-supabase/blocks/subjects";

import { useSubject } from "better-supabase/blocks/subjects/react";
import { RotateCwIcon } from "lucide-react";
import { useExtracted } from "next-intl";

import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { Link } from "@/i18n/navigation";

/**
 * A link to any subject in `config.subjects`, titled and routed by
 * `subject_titles`. The links rendered in one pass share one round trip, and a
 * subject the caller can't read shows as plain text.
 */
export function SubjectLink({ subject }: { subject: SubjectRef }) {
  const t = useExtracted("subjects");
  const { subject: resolved, error, refresh } = useSubject(subject);

  if (error) {
    return (
      <span className="flex items-center gap-1 text-sm text-muted-foreground">
        {t("The title didn't load")}
        <Button
          size="icon-xs"
          variant="ghost"
          aria-label={t("Retry")}
          onClick={() => {
            void refresh();
          }}
        >
          <RotateCwIcon />
        </Button>
      </span>
    );
  }

  if (resolved === undefined) {
    return <Skeleton className="h-4 w-32" />;
  }

  if (resolved === null) {
    return (
      <span className="truncate text-sm text-muted-foreground">
        {t("A record you can't open")}
      </span>
    );
  }

  const title = resolved.title ?? t("Untitled");

  return resolved.url === undefined ? (
    <span className="truncate text-sm font-medium">{title}</span>
  ) : (
    <Link
      href={resolved.url}
      className="truncate text-sm font-medium underline-offset-4 transition-colors hover:underline"
    >
      {title}
    </Link>
  );
}
```