Skip to content

[bug]: Base UI buttonVariants outline does not match Button outline unless wrapped with cn() #10874

Description

@Meenic

Describe the bug

When using the Base UI Button component, variant="outline" renders correctly.

However, when using the exported buttonVariants() helper directly on a Link, the outline variant can render visually like ghost because the generated class string contains conflicting border utilities.

The generated Base UI button includes border-transparent in the base classes:

const buttonVariants = cva(
  "group/button inline-flex shrink-0 items-center justify-center rounded-4xl border border-transparent ...",
  {
    variants: {
      variant: {
        outline:
          "border-border bg-background hover:bg-muted hover:text-foreground ...",
      },
    },
  },
)

Inside the Button component, the classes are wrapped with cn(...):

className={cn(buttonVariants({ variant, size, className }))}

So tailwind-merge resolves the conflict between border-transparent and border-border.

But when using buttonVariants() directly, the classes are not merged:

<Link
  href="/"
  className={buttonVariants({ variant: "outline" })}
>
  Back
</Link>

This can cause border-transparent to win over border-border, making the outline link-button appear like a ghost button.

Wrapping the result in cn(...) fixes it:

<Link
  href="/"
  className={cn(buttonVariants({ variant: "outline" }))}
>
  Back
</Link>

I would be happy to submit a PR. I think either of these fixes could work:

  1. Update the Base UI Button docs/examples to wrap direct buttonVariants() usage with cn(...).
  2. Move border-transparent out of the base button classes and into the variants that need it, so outline does not rely on tailwind-merge to remove a conflicting class.

Affected component/components

Button

How to reproduce

  1. Create/init a shadcn project:
bunx --bun shadcn@latest init
  1. Use the Base UI Luma preset.
  2. Add the Base UI Button component.
  3. Use the normal Button component:
<Button variant="outline">
  Back
</Button>
  1. Use buttonVariants() directly on a Link:
<Link
  href="/"
  className={buttonVariants({ variant: "outline" })}
>
  Back
</Link>
  1. Observe that the Button renders with an outline, but the Link can render visually like the ghost variant.
  2. Wrap the buttonVariants() call with cn(...):
<Link
  href="/"
  className={cn(buttonVariants({ variant: "outline" }))}
>
  Back
</Link>
  1. Observe that the Link now visually matches the Button.

Codesandbox/StackBlitz link

Minimal repro repo: https://github.com/Meenic/repro-shadcn-buttonvariants-outline.git
Live repro: https://repro-shadcn-buttonvariants-outline.vercel.app/

Logs

No runtime error. This is a visual/class-merging issue.

System Info

OS: Windows 11
bun: 1.3.3
shadcn: 4.10.0
Next.js: 16.2.6
Tailwind CSS: 4
Base: Base UI
Style: Luma
Browsers: Microsoft Edge, Chrome

Before submitting

  • I've made research efforts and searched the documentation
  • I've searched for existing issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions