Architecture

How Stisla is put together. The packages, the CSS cascade layers, the source tree, and how a runtime composes on top of the shared spec.

What this page covers

Stisla is a design specification with multiple implementations. What every implementation must honor lives in Specification, including the component anatomy and the token contract. The surface you tune lives in Theming. The reasoning behind avoiding Bootstrap, Tailwind alone, and CSS-in-JS lives in Why Stisla. This page covers the structure that holds it together: the packages, how the CSS bundle is layered, where the source lives, and how a runtime composes on top.

The structure is shared across implementations. The vanilla implementation is the reference, and its runtime specifics live on the JavaScript page; future implementations (React + Base UI, Vue + Reka, Svelte + bits-ui) get their own runtime pages as they land.

Packages

Stisla ships as a set of scoped packages. They track the same version line, so the stylesheet and runtime stay in sync when you install a matched pair.

PackageWhat it ships
@stisla/styleThe framework-agnostic source of truth: the Tailwind @theme foundation (theme.css), the per-component BEM CSS, and the composer (TypeScript). Every other package builds from this.
@stisla/cssThe pre-compiled universal stylesheet. Built from @stisla/style. No JavaScript, no build step to consume. Works in every implementation.
@stisla/vanillaThe vanilla JS runtime. Drives data-stisla-* behavior on canonical markup. Ships one entry that registers every component, and pairs with @stisla/css at the matching version.
@stisla/reactReact component wrappers built on Base UI. Consumes @stisla/style and the shared composer.

The Tailwind @theme foundation lives inside @stisla/style as its theme.css file. The pre-compiled @stisla/css bakes it in for zero-build consumers; build-from-source consumers import it directly. Setting up overrides covers both paths from the user side.

CSS bundle composition

Tailwind v4 organizes the compiled CSS into cascade layers. The order is:

Order matters. The theme layer holds the tokens. The base layer holds the Preflight reset and base element rules. Component rules sit above both. Utilities sit on top, so a utility class wins against the component it tweaks. Within a layer, normal cascade rules apply.

What each layer holds

LayerContentsSource
themeThe @theme token declarations (--color-*, --radius-*, …) and the dark-mode delta blockpackages/style/src/theme.css
baseTailwind Preflight reset plus the base body ruleTailwind (@import "tailwindcss") + theme.css
componentsEvery BEM component file. One file per blockpackages/style/src/<name>/<name>.css
utilitiesTailwind utilities (precompiled for the zero-build bundle, JIT for build-from-source consumers)Tailwind

The bundle

@stisla/css ships one precompiled stylesheet, stisla.css, with every component, the tokens, and no utilities. To ship a subset instead, compile from source with @stisla/style, which the Optimization page covers. Three components depend on a 3rd-party library: carousel (Embla), combobox (Tom Select), scroll-area (OverlayScrollbars). They ship in the bundle like everything else.

Source layout

Every Stisla implementation reads from the same source tree. The layout is flat by intent. Every spec’d component lives next to every other, regardless of whether one implementation classifies it as core or optional.

packages/

The repo also carries the cross-implementation contract files at the root. SPEC.md holds the shared contract. spec/components/<name>.md holds the per-component contract (anatomy, states, behavior, a11y, tokens) that every implementation honors.

The runtime layer

The CSS is static; behavior comes from a runtime layer that each implementation supplies its own way. The reference vanilla runtime ships as @stisla/vanilla, around 600 lines of our own code plus a few vendored primitives, and drives data-stisla-* markup through a register-then-scan lifecycle. The JavaScript page documents how it boots and its full API; the Interactivity contract is what any runtime has to satisfy.

Other implementations swap the layer wholesale. React leans on Base UI’s headless primitives, Vue on Reka. The CSS underneath is identical, so a data-state="open" dialog paints the same regardless of which runtime opened it.

How this realizes the spec

The vanilla impl is one realization of the cross-implementation contract. Reading the spec alongside the source shows how each requirement lands in practice. A few examples:

Spec requirementVanilla solution
Token surface is one Tailwind @theme layerpackages/style/src/theme.css. Only file with literal color values.
State derivation via color-mixEvery component CSS file. Recipe percentages are CSS custom property defaults, e.g. color-mix(in oklch, var(--button-tone) 88%, black)
BEM block names match the spec.button, .card, .dialog, etc. One <name>.css per block under packages/style/src/
State attributes match primitive-library conventions[data-state="open|closed|active"] set by the vanilla JS or by future primitive libraries; same CSS serves both
Focus, dismiss, scroll-lock, keyboard a11yfocus-trap (dialog, drawer), our own dismiss and scroll-lock, Floating UI for floating elements
Dark mode is one delta blockEnd of theme.css. [data-theme="dark"] and .dark both match
Component contract per spec/components/<name>.mdEach JS-coordinated component honors the spec’s required behavior, parts, and events

Future implementations (React, Vue, Svelte) get their own runtime pages with the same shape. Same spec, different runtime, different primitive library, same @stisla/css underneath.

What’s next

Read the Specification for the cross-impl contract that every implementation honors, including the component anatomy and token rules. Read Theming for the override model and the worked recipes. Open any component file under packages/style/src/ for the canonical pattern in practice; button/button.css, card/card.css, and alert/alert.css are the cleanest references.