Style presets vs hand-tuned CSS in shadcn projects
What a shadcn style preset carries, what hand-editing globals.css costs you over time, and how to decide which one your marketing site should run on.
· Initium Team · 7 min read
Every shadcn project reaches the same fork. You have components installed, the default palette looks like the default palette, and you need the site to look like yours. One path is opening globals.css and editing tokens until the preview matches what you had in mind. The other is installing a style preset that arrives with those decisions already made and already consistent.
Both produce a themed site. They differ in what happens on the second day, when a new block lands, a dark mode bug appears, or an agent edits a page you did not write.
What is a style preset in a shadcn project?
A style preset is a registry item that carries theme decisions instead of a component. In the shadcn registry schema it is typed registry:style or registry:theme, and its payload is mostly CSS variables rather than TSX (registry-item.json docs).
The variables live under a cssVars field split three ways: theme for base tokens, light for the :root values, and dark for the .dark overrides. The registry docs show the shape directly:
{
"name": "custom-theme",
"type": "registry:theme",
"cssVars": {
"theme": { "font-heading": "Poppins, sans-serif" },
"light": { "brand": "20 14.3% 4.1%", "radius": "0.5rem" },
"dark": { "brand": "20 14.3% 4.1%" }
}
}A style item can also carry a css field, which writes real rules into @layer base, @layer components, @utility, or @keyframes (registry examples). That matters more than it sounds, because a visual system is not only colors. It is also what an h1 does at the base layer, what a shared .wrapper utility sets, and how a tagline is cased and weighted.
When you install one, the CLI writes those values into your project's CSS file. The registry FAQ puts the mechanism plainly: to add a new color you add it to cssVars under light and dark, and "the CLI will update the project CSS file" so the new colors become available as utility classes (registry FAQ). Nothing is hidden in a dependency. The tokens end up in your repo, in a file you can keep editing.
What does hand-tuning globals.css actually cost?
Hand-tuning is not wrong. It is a maintenance shape.
shadcn's theme system is built on semantic pairs: a base token controls a surface, and the matching -foreground token controls text and icons on that surface, with the same names redefined inside a .dark selector for dark mode (theming docs). Since the Tailwind v4 alignment, those values are written in OKLCH and exposed to utilities through an @theme inline block (Tailwind v4 guide).
That is a well-designed system, and it is also a lot of paired edits. Every color you touch by hand has at least four correct places to be touched: the base token and its foreground, in light and in dark. Miss one and nothing breaks loudly. You get a card that reads fine until someone toggles the theme, or a muted surface that drifted two edits away from the one next to it.
The second cost is scope creep in the wrong file. Radius is the clearest example. shadcn derives a whole scale from a single --radius, with smaller radii scaling down and larger ones scaling up, so changing that one variable moves the entire shape language (theming docs). Once someone hardcodes rounded-xl on a card because it looked better that afternoon, the scale no longer describes the site. The token is still there. It has stopped being the source of truth.
Tailwind v4 makes the same point from the other side. Theme variables are not ordinary CSS variables: they define which utilities exist. The Tailwind docs are explicit that theme variables do more than hold values: they instruct Tailwind to create the utility classes you write in your markup (Tailwind theme docs). Editing them by hand is editing your utility vocabulary, one line at a time, usually at the end of a long day.
What does a preset carry that a color picker does not?
Picking a primary color is the easy part of a visual direction, and it is the part most theming flows stop at. The decisions that make a marketing page look designed sit slightly below the palette:
- Type scale, and the relationship between a hero heading, a section heading, and body copy.
- Heading and body font families, with the specific weights that pairing needs.
- Radius scale, applied consistently across cards, buttons, forms, and containers.
- Button treatment, including the weight of button text, which is set inside the component rather than in a token by default.
- Form field treatment, so inputs match buttons instead of approximating them.
- Marketing texture: what a tagline looks like, how body sections set their measure.
In Initium the preset is a registry:style item that carries all of that together. It writes :root and .dark token blocks, adds base-layer rules for headings, wrapper, tagline, and paragraph utilities, ships the button and form components already tuned to the chosen weight, and pulls the fonts in as registry dependencies rather than leaving you a font import to remember. The styles documentation covers the full contents.
The practical effect is that the decisions arrive as a set. A palette plus a mismatched radius plus a default button weight is three unrelated choices sitting on one page, which is exactly what "looks generated" describes.
How do you install one?
Install the style before any blocks, because blocks depend on the shared utilities the style defines. The base pack:
npx shadcn@latest add @initium/initium-stylesOr a preset exported from the Style Builder, which encodes your specific selections in the URL:
npx shadcn@latest add "https://app.initium.sh/r/preset?code=..."The code value is generated. It is not meant to be edited by hand. If the @initium namespace prefix is unfamiliar, the mechanics of namespaces and what an install actually writes are covered in shadcn registries explained.
Blocks come after:
npx shadcn@latest add @initium/hero-section-1What should you check after installing a preset?
A preset writes to files you own, which means the install is a diff worth reading rather than a black box worth trusting.
Check that both token blocks landed. Your CSS file should have the new values in :root and the matching set inside .dark. A preset that appears to work in light mode and reverts to defaults in dark usually means only half the tokens were written.
Check the @theme inline mapping. Tokens that are not exposed through it exist as CSS variables but generate no utilities, so bg-brand will not do anything even though --brand is defined.
Check that the preset landed once. If you had already hand-edited tokens, you can end up with two definitions of the same variable in one file and the later one silently winning. Delete the stale block rather than layering over it.
Then look at a real page, in both modes, at a small breakpoint. Presets are theory until a hero section with a tagline, a heading, body copy, and two buttons renders all at once.
When is hand-tuning still the right call?
When the change is genuinely local and genuinely one-off. A single section that needs a different background is a one-line utility, not a token. Prototypes are another fair case: if the site will be rewritten next week, the system you are protecting does not exist yet.
The line worth holding is between adjusting a token and bypassing it. Changing --radius because the shape language should shift is the system working. Hardcoding a radius on one card because that card needed it is the beginning of a site with no shape language at all.
Why does this matter more with an agent in the loop?
An agent editing your marketing page has no visual memory. It reads the code it can see and continues the patterns it finds. If your styling lives in tokens, the patterns it finds are correct by construction: it writes bg-card, rounded-lg, text-muted-foreground, and the result matches everything around it.
If your styling lives in accumulated one-off overrides, the agent learns the overrides. It sees a hardcoded radius and treats it as the house style, then reproduces it in the next three sections. Drift compounds faster with an agent than without one, precisely because the agent is consistent about whatever it was shown.
This is why a style preset pairs naturally with the rest of the system. Tokens tell the agent what the site is made of. A DESIGN.md export tells it which of those decisions were intentional. Skills tell it how to use blocks without breaking either.
If you have been theming by hand and the site has started drifting, the fastest reset is to export a preset that matches the direction you actually want, install it before your next block, and let the tokens do the arguing. The Style Builder and styles docs are the place to start.
Build a site that looks designed
Initium gives your coding agent the blocks, style presets, and skills behind everything on this blog.
Try for freeMore from the blog
shadcn registries explained: installing blocks you own
What a shadcn registry actually is, what happens when you run npx shadcn add, how namespaces and private registries work, and why agents like them.
6 min read
From zero to a full website with Initium: a walkthrough
Blank folder to deployed marketing site: shadcn init, the Initium registry, a style preset, strategy files, blocks, and the prompts that keep it designed.
6 min read
Why AI-built marketing sites look generated (and how to fix it)
Coding agents ship layouts fast, but the result reads as template soup. Here is what actually makes a marketing site look designed — and how Initium encodes it.
1 min read