shadcn-remix
GitHub

Introduction

Beautifully designed components for Remix 3. A faithful port of shadcn/ui, works with the shadcn CLI.

shadcn-remix is shadcn/ui for Remix 3: the same components, the same API and the same styles, written with remix/component and its css() mixin, with behavior from the @remix-run/ui headless primitives.

Philosophy

shadcn-remix follows the shadcn model: copy-paste components you own, with zero lock-in and sensible defaults that work immediately. It is not a component library you install — it is the source, distributed through a shadcn registry.

Remix 3 components are not React components. A component is a setup function that returns a render function, styles are mixins instead of class names, and interactivity hydrates per clientEntry. shadcn-remix keeps the shadcn/ui API — names, props, variants and data-* attributes — and translates only what the runtime requires.

Why shadcn-remix?

  • Own Your Code: Every component is one file in app/ui. Change anything.
  • Exact Port: Tailwind classes are translated declaration by declaration, so components match ui.shadcn.com pixel for pixel.
  • Server First: Components render on the server; only interactive ones hydrate.
  • No Build Step: Styles are generated by css() at render time — no Tailwind, no PostCSS.

Features

Zero Config
Works out of the box with sensible defaults. No build step, no Tailwind.
Theme Aware
Built on the shadcn/ui CSS variables, with light and dark themes.
Composable
The same component composition and data attributes as shadcn/ui.
TypeScript
Typed props for every component, down to each host element.
Copy & Paste
Own your code. Each component is a single file you copy into app/ui.
shadcn Compatible
Uses the shadcn registry format — add components with npx shadcn add.

From shadcn/ui to Remix

shadcn/ui (React)shadcn-remix
className="w-40"mix={css({ width: '10rem' })}
<Button asChild><a href="/" /></Button><a href="/" mix={buttonVariants()} />
<DialogTrigger asChild><Button /></DialogTrigger><DialogTrigger mix={buttonVariants({ variant: 'outline' })} />
useState + onCheckedChangelet checked = false + handle.update()
lucide-react#app/ui/icons.tsx (same icon names)