Tailwind CSS
@svelte-lean/styles and Tailwind CSS v4 share one cascade. Declare the layer order once, import both, map the tokens into Tailwind's theme, and a utility class overrides a primitive the way any unlayered rule does. The package does not depend on Tailwind.
On this page
Setup
Tailwind v4 puts its CSS in the cascade layers theme, base (preflight), components and utilities. Every file of this package declares its
rules in sublayers of one layer, slean (slean.reset, slean.tokens, slean.base, slean.components, slean.compositions, slean.utilities). A layer statement at the top of
the stylesheet places that layer between preflight and Tailwind's components:
/* src/app.css. The layer statement comes first: Tailwind's theme and preflight (base) sit
* below Svelte Lean, Tailwind's components and utilities above it. The package files declare
* their rules in slean.* sublayers, so they are imported without layer(). */
@layer theme, base, slean, components, utilities;
@import 'tailwindcss';
@import '@svelte-lean/styles/tokens.css';
@import '@svelte-lean/styles/base.css';
@import '@svelte-lean/styles/button.css';
@import '@svelte-lean/styles/input.css';
@import '@svelte-lean/styles/menu.css';
/* Tailwind's dark: variant follows the same class the tokens read. */
@custom-variant dark (&:where(.dark, .dark *));- The layer statement must come before
@import 'tailwindcss': the first statement that names a layer fixes its position. Without it,sleanis created afterutilitiesand the package rules win over every utility. - Import the package files without
layer(). They already declareslean.*sublayers; wrapping them in another layer would nest them one level deeper, under a layer the statement does not order. - Import only the files the page uses. A file that builds on another imports it itself (
menu.cssbringspopover.css); Tailwind's import inliner includes a file once.
Preflight
Preflight lives in base, below slean. Its resets (no border, no
padding, transparent button backgrounds, display: block on svg) apply
first and the package rules replace them on every element that carries data-slean or data-slean-part: a button keeps its accent fill and 36px height, an input its
border, a checkbox its drawn box. Content inside a primitive that is not a part (the body of a
dialog, a tab panel) keeps preflight, like the rest of the page.
Tokens as Tailwind colors
@theme inline turns the --slean-* tokens into Tailwind colors and
radii, so markup next to the primitives can use bg-primary, border-input or text-muted-foreground. inline matters: the utilities keep the var(), so they follow the dark palette and every theme scope instead of the value
at build time.
/* Tailwind colors and radii that read the Svelte Lean tokens. inline keeps the var() in the
* generated utilities, so a theme scope or the dark palette changes them at run time. */
@theme inline {
--color-background: var(--slean-bg);
--color-foreground: var(--slean-fg);
--color-surface: var(--slean-surface);
--color-muted: var(--slean-muted);
--color-muted-foreground: var(--slean-fg-muted);
--color-border: var(--slean-border);
--color-input: var(--slean-control-border);
--color-ring: var(--slean-focus-ring-color);
--color-primary: var(--slean-accent);
--color-primary-foreground: var(--slean-accent-fg);
--color-destructive: var(--slean-danger);
--radius-sm: var(--slean-radius-sm);
--radius-md: var(--slean-radius-md);
--radius-lg: var(--slean-radius-lg);
}The names follow the shadcn convention; any other naming works the same way. The reverse direction is an ordinary token override that points the package at Tailwind's theme variables:
/* The other direction: point the Svelte Lean tokens at Tailwind's theme variables, so the
* primitives take the palette of an existing Tailwind design. Unlayered, like any override. */
:root {
--slean-accent: var(--color-indigo-600);
--slean-accent-hover: var(--color-indigo-700);
--slean-accent-active: var(--color-indigo-800);
--slean-accent-soft: var(--color-indigo-50);
--slean-accent-soft-fg: var(--color-indigo-700);
--slean-font-sans: var(--font-sans);
}Dark mode
The tokens switch to the dark palette under prefers-color-scheme: dark, on data-theme="dark" and on the dark class, on the root or on any subtree
(Themes). The @custom-variant line in the
setup gives Tailwind's dark: variant the same class, so a class written by a toggle or
by mode-watcher switches both.
Overriding a primitive with utilities
The utilities layer comes after slean, so a utility wins over the
package declaration it meets, whatever the package selector is. The package styles its states (:hover, :focus-visible, :disabled) with rules of their own; a utility
without a variant replaces the property in every state, so pair it with the variant, as hover:bg-emerald-700 does here.
<!-- The utilities layer comes after slean, so a utility wins over the package rule it
meets, whatever the selector. A state the package styles needs its own variant: a plain
bg-* also replaces the hover background. -->
<button
type="button"
data-slean="button"
class="rounded-full bg-emerald-600 px-8 shadow-lg hover:bg-emerald-700"
>
Publish
</button>
<input type="text" data-slean="input" class="w-64 rounded-none border-2 border-primary" />
<!-- Tailwind's own markup can use the token colors from @theme inline. -->
<p class="rounded-md border border-border bg-muted px-2 py-1 text-muted-foreground">…</p>Note A utility changes one element. To change every button, override a token (--slean-radius-md, --slean-accent) or write an unlayered rule on the protocol attribute; both reach
the primitives that are not in your markup, such as the rows of a select's list.