# Pasting a shadcn component into a repo that renamed the tokens

> shadcn components are written against variable names, not values. Publish those names alongside your own and a paste works unmodified: except for the ones you already claimed.

You copy a dropdown menu out of the shadcn/ui docs into your project. It
compiles, it opens, and it is invisible: white text on a white panel, with a
menu item that turns into a grey slab on hover.

Nothing in the component is broken. It is asking for variables your project does
not publish.

## Why it happens

A shadcn component contains no colours. It contains *names*:

```tsx
<div className="bg-popover text-popover-foreground border-border shadow-md">
  <div className="focus:bg-accent focus:text-accent-foreground">Profile</div>
</div>
```

Those utilities exist only if your Tailwind theme defines `--color-popover`,
`--color-popover-foreground`, `--color-border`, `--color-accent` and
`--color-accent-foreground`. In Tailwind v4 an unknown utility is not an error:
it simply is not generated, so the element renders with no background at all and
inherits whatever is behind it.

If your project named the same concepts differently (`canvas`, `ink`,
`hairline`, `surface-card`) every one of those classes is a no-op.

## The fix: publish both sets of names

You do not have to choose. A colour token is a variable, and a variable can have
two names pointing at one value.

Declare the palette once, using shadcn's names as the raw layer:

```css
:root {
  --background: #ffffff;
  --foreground: #09090b;
  --card: #ffffff;
  --card-foreground: #09090b;
  --popover: #ffffff;
  --popover-foreground: #09090b;
  --primary: #18181b;
  --primary-foreground: #fafafa;
  --destructive: #dc2626;
  --border: #e4e4e7;
  --input: #e4e4e7;
  --ring: #18181b;
}
```

Then map both naming systems onto it in one theme block:

```css
@theme inline {
  /* your names */
  --color-canvas: var(--background);
  --color-ink: var(--foreground);
  --color-hairline: var(--border);

  /* shadcn's names, same values */
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary-foreground: var(--primary-foreground);
  --color-destructive: var(--destructive);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
}
```

Now `bg-canvas` and `bg-background` are the same rule with two names, and a
pasted component works untouched. Use `@theme inline` rather than `@theme`: it
emits `background-color: var(--background)` instead of copying today's hex, so
the utilities follow a dark-mode swap.

The cost is one block of aliases, written once. The benefit is that the entire
shadcn catalogue becomes copy-paste for your project, forever.

## The names you cannot bridge

Sometimes a name is already taken and means something else in your system. Two
are common:

**`muted`.** Upstream, `--muted` is a light grey *surface* and
`--muted-foreground` is the secondary *text* colour. Plenty of design systems use
`muted` for the text colour directly, so `text-muted` already exists and means
#71717a. You cannot have `bg-muted` be a pale surface and `text-muted` be a mid
grey: one variable, one value.

**`accent`.** Upstream, `--accent` is the hover surface for menu items. Many
systems use `accent` for their one saturated brand colour. Again, one variable.

You have to pick, and the right answer is almost always **keep your own
meaning**, because your codebase already has dozens of usages and the paste has
one or two. Then write the translation down where a person doing the paste will
see it:

| Upstream class | Here | Paste this instead |
|---|---|---|
| `bg-muted` | `muted` is the muted text colour | `bg-surface-card` |
| `bg-accent` / `text-accent-foreground` | `accent` is the saturated brand colour | `bg-surface-card` / `text-ink` |

Two classes, and they cluster: they appear in menu items, command palettes and
list rows. A grep on the way in catches them:

```bash
grep -nE "\b(bg|text|border)-(muted|accent)\b" src/components/ui/dropdown-menu.tsx
```

Note the word boundary. `text-muted-foreground` and `bg-accent-soft` are
different tokens and are fine; only the bare names collide.

## Other things a paste brings with it

**`cva` and `tailwind-merge`.** Upstream examples often use
`class-variance-authority` and `cn()`. If your repo deliberately carries neither,
rewrite the variant object as a plain lookup:

```tsx
const variants: Record<Variant, string> = {
  default: "bg-primary text-primary-foreground",
  outline: "border border-input bg-background",
};
```

It is the same information with one fewer dependency, and it stays readable up to
about five variants.

**Radix packages.** The interactive components genuinely depend on
`@radix-ui/react-*`. Add the package properly rather than deleting the import
and hand-rolling a focus trap: accessible menus are harder than they look.

**Hard-coded values in examples.** Docs examples sometimes carry an arbitrary
value or a palette class from the demo page. A lint that fails on raw colours
catches these at the door, which is a good reason to have one.

## How to know it worked

Render the pasted component and check three things: it has a background, its
border is visible, and its hover state is a surface rather than a saturated
block. Then check it again in dark mode if you have one: a bridged token
follows the mode, a missed one does not, which makes dark mode an unusually good
detector for classes you forgot to translate.

---

Agentic Boilerplate: A Next.js repo your agent already knows. Free during launch, then $99 once.

- Site map for agents: https://agenticboilerplate.com/llms.txt
- Public API: https://agenticboilerplate.com/openapi.json
- Contact: agenticstudio@gmail.com
