The slabui CLI is in development. Commands, options and the registry format
may change before version 1.0.
slabui init splits your CSS into four files. The global CSS is the css file
from slabui.json, wherever it is; the other three go to core/, in the
folder of the styles alias, and the global CSS imports them by their relative
path.
styles/
├── globals.css ← the theme: :root, .dark and @theme inline
└── core/
├── fonts.css ← typography: font families and the text-* scale
├── base.css ← element styles (*, html, body) in @layer base
└── utilities.css ← @utility and @custom-variant
@import "tailwindcss";
@import "./core/fonts.css";
@import "./core/base.css";
@import "./core/utilities.css";
@custom-variant dark (&:is(.dark *));
@theme inline {
/* radius, spacing and --color-* */
}
:root {
--font-size: 1rem;
/* light theme variables */
}
.dark {
/* dark theme variables */
}The global CSS holds only the theme, so it is the file the
theme editor reads and writes. Styles that are
not theme variables live in the other files, where update can change them
without touching your colors.
Text size
Every text-* class is proportional to --font-size, the size of
text-base. Change it in :root (or at runtime, on <html>) and all text
grows or shrinks together; spacing and heights stay the same.
@theme inline {
--text-xs: calc(var(--font-size) * 0.75);
--text-xs--line-height: calc(1 / 0.75);
--text-sm: calc(var(--font-size) * 0.875);
--text-sm--line-height: calc(1.25 / 0.875);
--text-base: var(--font-size);
/* … up to --text-9xl */
}Line heights are unitless ratios, so they follow the font size on their own.
Keep them unitless: a 1.25rem line height stays fixed while the text grows.
Arbitrary sizes such as text-[0.625rem] do not go through the theme and do
not follow --font-size.
--font-size is in rem, so it respects the font size set in the browser.
Use it for a "text size" setting for your users, for example 1.125rem for
larger text.
Where each thing goes
| What it is | File |
|---|---|
Theme variables, @theme inline, the dark variant | globals.css |
Font families and the text-* scale | fonts.css |
Styles for elements in the whole app (*, body) | base.css |
Reusable @utility and @custom-variant | utilities.css |
| CSS that exists only because of one component | styles/ui/<component>.css, a file of the item |
| Choices of your app's layout | Your own CSS, not the registry |
Keep scrollbar, * and other element styles inside @layer base. In
Tailwind 4, CSS outside a layer always wins over the layers, utilities included:
a * { scrollbar-width: thin } outside @layer base breaks no-scrollbar.
What add writes
Each item can bring, besides its files:
| Item field | Written to | Merged by |
|---|---|---|
cssVars | globals.css (--font-* and --text-* in fonts.css) | Variable name |
utilities | utilities.css | Utility name |
variants | utilities.css | Variant name |
base | base.css | Selector and property |
The theme variables an item of the SlabUI registry uses (bg-muted,
var(--focus)) are added to globals.css with the default values when they
are missing. Values you already
have are never changed.
When utilities.css or base.css does not exist, add creates it in
styles/core/ and adds the @import to the global CSS.
An existing global CSS
When the project already has a global CSS, such as the app/globals.css of
Next.js or the src/index.css of Vite, init replaces its content with the
SlabUI template and saves the original next to it, as globals.css.bak (or
.bak.1, .bak.2… when a copy already exists). In an interactive terminal it
asks first; answer No to keep the file and only add the missing theme
variables. With --yes, it replaces the file.
The global CSS stays where it is, and the imports point to styles/core/:
@import "tailwindcss";
@import "../styles/core/fonts.css";
@import "../styles/core/base.css";
@import "../styles/core/utilities.css";Copy what you still need from the .bak file (custom classes, extra imports)
and delete it.
A project set up by an earlier version, with these files next to the global
CSS or directly in styles/, keeps using them where they are.
update merges utilities, variants and base declarations like the variables:
new ones are added, the ones you did not change follow the new version, the
ones you changed are kept with a warning, and the ones removed in the new
version are kept unless you confirm.
In the registry
An item lists the utilities and variants it uses by name. The build reads
their code from the author's global CSS (css in the entry) and the local
files it imports, so each utility is written once:
{
name: "sidebar",
kind: "ui",
files: [{ path: "ui/sidebar.tsx", kind: "ui" }],
utilities: ["no-scrollbar"],
variants: ["fixed"],
// Only when there is no other way: it changes the whole app.
base: { body: { overflow: "hidden" } },
}A utility or variant that is not in the author's CSS stops the build with an
error. The theme variables a utility uses (@apply bg-muted) count as tokens of
the item.