Getting started
SilicaUI is a Base UI behavior layer with CSS-first styling. Install the package, bring your own design tokens, and keep the primitives.
Requires Tailwind CSS v4. The @plugin directive below is v4 syntax; on v3 it
will not compile. If you are starting fresh, create-next-app --tailwind or
create-vite + @tailwindcss/vite both give you v4.
1. Install
npm install @wizeworks/silicaui @wizeworks/silicaui-react
@wizeworks/silicaui is the CSS — the Tailwind plugin, the tokens, the component
classes. @wizeworks/silicaui-react is the React layer on top of it. Using plain
HTML or a node tree instead? See the other two paths.
2. Register the plugin
Add it to your CSS entry point — the file that already has @import "tailwindcss".
In a Next.js App Router app that is app/globals.css; with Vite it is usually
src/index.css.
@import "tailwindcss";
@plugin "@wizeworks/silicaui" {
colors: primary, secondary, accent, neutral, info, success, warning, error;
}
Every name in colors: gets the full treatment — btn-primary, badge-primary,
text-primary, border-primary and the rest — with no rebuild and no safelist. Add
your own names to that list and they behave identically to the built-in ones.
If you declare a color in @theme and forget to list it here, SilicaUI tells you at
build time, names the variants you will not get, and prints the line that fixes it.
3. Use a component
No separate theme file, no config object. In a Next.js App Router app, this is
app/page.tsx:
import { Button } from "@wizeworks/silicaui-react";
export default function Page() {
return (
<main className="p-8">
<Button color="primary">Get started</Button>
</main>
);
}
The <main> wrapper is not decoration: create-next-app scaffolds
<body class="flex flex-col">, so a lone child stretches the full width of the
window and the button will look wrong without something to sit in.
That is the whole setup. See the component reference for the full catalog, each with a live, interactive demo, a props table and the import line for every path.
4. Themes
A theme is a bag of token values applied with the data-theme attribute. Put it
on <html> for the whole page, or on any wrapper for one section — nesting is the
intended idiom, and a section needs no CSS of its own.
<html data-theme="dark">
<section data-theme="light">a light island inside a dark page</section>
</html>
The plugin emits two names: light and dark. There is no .dark class and no
second stylesheet — dark is a theme. Any other name is one you declare:
@plugin "@wizeworks/silicaui/theme" {
name: marble;
color-scheme: light;
--color-base-100: oklch(98% 0.002 250);
--color-primary: oklch(45% 0.04 250);
/* …any Silica token: --radius-box, --border, --depth, the type faces */
}
Load it after the main plugin so your values win by source order. Partial
overrides are fine — anything you leave out falls through to the built-in value, and
any --color-X without a matching --color-X-content gets a legible ink derived for
it automatically.
Two things worth knowing before you spend an afternoon on it:
- An undeclared name fails quietly.
data-theme="marble"with no marble declared is not an error — it matches the bare[data-theme]rule, every token resolves to the default, and you get a page that looks fine and is not the theme you asked for. If a theme seems to do nothing, check that you declared it. - To follow the OS instead of choosing, add
prefersdark: trueto a theme block (or to the main plugin block) and it applies under@media (prefers-color-scheme: dark)for a page that carries no explicitdata-theme. No script, no flash.
A dialog opened inside an island
Nesting holds for everything rendered in place. It does not hold for a popup,
because a dialog, menu, select, tooltip or toast is portalled to document.body to
escape overflow and stacking contexts — so its nearest [data-theme] is the one on
<html>, not the island it was opened from. Nothing errors; the panel simply comes up
in the page's theme.
Two ways to say what you meant, depending on how much you are theming:
// ONE popup — re-state the theme on the popup's own root.
<Select popupProps={{ "data-theme": "atelier-ink" }} … />
// A WHOLE REGION — point every portalled surface below it at an element inside
// the island, and they all resolve against it with no per-component props.
<PortalContainerProvider container={islandEl}>
…the island's contents…
</PortalContainerProvider>
PortalContainerProvider is the general answer and covers components that take no
popupProps. The container must not clip (overflow: hidden) or establish a
containing block for fixed positioning (transform, filter, contain,
backdrop-filter), or a centered popup positions against the container instead of the
viewport — a plain position: relative wrapper is safe.
The twenty named presets — their palettes, type faces and shape tokens — are served by
the Silica MCP server: list_themes for the
set, get_theme("marble") for one preset's full token map to paste into the block
above.
Without React
React is one output, not the product. The same components exist on two other paths, and every component page documents all three.
Plain HTML — CSS classes only. Install @wizeworks/silicaui, register the plugin
exactly as in step 2, and write the classes on your own markup. Works in Rails,
Django, PHP, Go, or a static file, with no bundler and no JavaScript at all — which
also means a component that needs interaction will be styled but inert.
<button class="btn btn-primary">Get started</button>
A framework-neutral node tree. For generated or user-authored documents — a site builder, CMS output, a static export — build a node tree, project it to HTML, and let the zero-dependency runtime hydrate it. This gives you real keyboard handling and focus management on a static page.
npm install @wizeworks/silicaui-html @wizeworks/silicaui-behaviors
import { atom, toHtml } from "@wizeworks/silicaui-html";
// atom(component, className, props, children)
const markup = toHtml(atom("Button", "btn btn-primary", { label: "Get started" }));
// → <button class="btn btn-primary" type="button">Get started</button>
Note where the styling goes: on this path it is the class string, the same one you
would write by hand in the Plain HTML section above. There is no color prop here —
that is the React layer's convenience.
Load @wizeworks/silicaui-behaviors on the page, or interactive markup renders
correctly and does nothing.