Introduction

A quick read of what Stisla is and why it’s built this way.

What Stisla is

Stisla is a design specification for user interfaces.

A spec is a written set of decisions you agree on before anyone writes code. HTTP, Markdown, and OpenAPI all work this way. Anyone can build a server, a parser, or a generator, and they all agree because the spec is the contract. The implementation is one valid way to honor it.

The Stisla spec is the contract for a UI. It fixes the token names every component reads from. It fixes the anatomy of each component (a card has a header, body, and footer; a dialog has a backdrop and content). It fixes the states every interactive surface answers to (rest, hover, active, focus, disabled, loading). It fixes the radius and density scales, the light and dark deltas, and the way a brand override flows through hover and focus. Stable names that you write once and reuse.

The spec is framework-agnostic on purpose. The first implementation ships as vanilla CSS plus a small JavaScript runtime built on Floating UI, focus-trap, and Embla. React, Vue, and Base UI ports come later, all against the same spec. A button in any future port will agree with the vanilla one on radius, density, hover, and focus.

The full surface lives on the Spec page.

What Stisla isn’t

It isn’t a utility-class library. Stisla ships real components with BEM classes like .btn, .btn--primary, .card, and .card__header. When you read the HTML, you see what the page does instead of a long list of style classes.

It isn’t a wrapper around Bootstrap. Stisla v2 was Bootstrap 4, but v3 dropped Bootstrap and rebuilt every component from scratch. The only forked code is the grid and breakpoint mixins, both credited under LICENSES/. The rest is hand-written.

And it doesn’t come in different flavors or themes. Stisla has one spec. Density, radius, and brand are knobs you turn on top of that one spec.

Why this approach

Two ideas shape the rest of the system. Stisla is spec-first and runtime-first.

Spec-first means every implementation should feel like the same product. A button in the React port and a button in the vanilla port agree on what they mean. They don’t just happen to look alike. The only honest way to keep that true is to write the spec before any of the implementations. v3 is one implementation. Future ports fill the same spec.

Runtime-first means most customization should not need a build step. Pick a primary color, set a radius, adjust density, or drop a brand-tinted preset on a wrapper class. Each one is a single CSS variable override that flows through var(--st-*) and color-mix(in oklch, …). The harder cases (changing breakpoints, dropping unused components, rewriting a component) still need Sass.

Why not Bootstrap

We tried. Stisla v2 was Bootstrap 4, and the original plan for v3 was Bootstrap 5.3. Both were a rough fit, and the trouble was always in the same place. The token surface.

Bootstrap mixes Sass variables (compile time), CSS custom properties (runtime), and SCSS functions that read from theme maps. When you override a color at runtime, it often doesn’t reach the code that derives the hover state, because that code runs at build time in Sass. So your override goes one layer deep and stops. The button repaints, but the hover stays the old color. The alert tint updates, but the focus ring is left behind.

We wanted overrides to flow all the way through. Pick a primary color and get a matching hover, a matching active state, a matching focus ring, and matching soft fills. No rebuild. For that to work, every derivation has to happen at runtime, which means color-mix(in oklch, …) instead of Sass color functions, and a flat token surface that components read from directly.

The trade-off is that breakpoints can’t live in CSS variables, because media queries can’t read them. Stisla accepts that. Breakpoints are the only Sass-level knob. Everything else runs at runtime.

Why not Tailwind

A good design system needs a small, fixed set of choices. You should know what colors you ship, what buttons you ship, and what components exist. Those constraints are what make it a system.

Tailwind has a wide constraint surface. That helps when you’re prototyping. You can reach for any gray, any padding, any radius without breaking flow. The same thing hurts when you’re building a system. A new component arrives, the author picks gray-400 because they haven’t decided what the component should actually be tinted with, and the codebase grows another inconsistent shade. Multiply that across a team and the design system drifts into “every component does its own thing inside the same utility vocabulary.”

You can tune Tailwind into a real design system. Lock the theme to your tokens, restrict the palette, document the constraints. By the time you’ve done that work, you’ve recreated what a hand-built BEM system gives you on day one. A fixed vocabulary you commit to. Same work, same result.

The other half is taste. Stisla prefers BEM components over assembling utilities. A .btn is a thing, and .btn--primary is a variant of it. Reading the HTML tells you what the page is.

One trade-off here. Stisla’s CSS bundle is larger than the equivalent Tailwind output. That’s how CSS has always worked. It pays for itself by being cacheable, rendered in parallel, and editable without a JIT. Bundle size matters, but it isn’t the only thing that matters.

Status

The current version is 3.0.0-beta.1. The token surface, BEM class names, and component APIs are stable as of this beta unless a bug forces a change. NPM packaging, the Installation page, and bundle-size guidance will land before 3.0.0 stable.

v2, the Bootstrap 4 release, is not getting migrated and stays at getstisla.com. The two versions are different enough that there’s no automatic migration path.