Docs / Styling & UI / Components & shadcn/ui
Components & shadcn/ui
components/ui/* is stock shadcn/ui, generated through its CLI, not hand-built. components.json at the repo root configures it: style: "radix-sera" as the base, baseColor: "taupe", Lucide icons. shadcn isn't a package dependency, the CLI copies component source directly into your repo, so everything in components/ui/ is plain code you own outright, the same as anything else in the codebase.
Keel's visual identity sits on top, not inside
The Sera base style renders buttons and badges uppercase, wide-tracked, and square-cornered, a deliberately different look from Keel's warm, rounded, editorial design. Rather than hand-edit the generated primitives (which npx shadcn add would overwrite on a re-run), Keel overrides them at the call site with shared class fragments:
// components/marketing/shared.ts
export const keelButtonPrimary =
"h-11 rounded-[10px] px-6 normal-case tracking-normal text-[0.96rem] font-semibold ...";
Every marketing CTA and most in-app buttons pass this into className alongside the shadcn Button. This keeps components/ui/* re-generatable, running npx shadcn add <component> to pull in a new primitive, or diff against upstream, never fights hand-edits you'd lose.
Adding a component
- A new shadcn primitive (a dialog variant, a data table, anything in shadcn's registry):
npx shadcn add <name>, then apply the same override pattern above if it needs to match Keel's rounded/warm look rather than Sera's default. - A custom, one-off component: it doesn't belong in
components/ui/, that folder is reserved for CLI-managed primitives. Put it in the domain folder it belongs to instead,components/marketing/,components/app/,components/admin/, matching Project structure's "organize by domain" convention. A component used by exactly one page goes in that page's local_components/folder instead of a sharedcomponents/directory.
If you want to remove shadcn entirely
There's no single switch, components/ui/* is used across the marketing site, dashboard, settings pages, and admin panel, and each file depends on Radix primitives and class-variance-authority for variants. Since every file is plain code already in your repo though, nothing forces you back through the CLI: edit, rewrite, or delete any given primitive the same as any other component, the CLI is only a scaffolding tool you used once, not a runtime dependency you're locked into. Swapping the whole system out means replacing each primitive's internals while keeping its exported name and props stable, so callers don't need touching.