Installation
Stisla ships in two pieces. A universal stylesheet and a vanilla JS runtime. Drop them in via CDN, install from npm, or fork the Sass source.
Pick a bundle
Stisla ships two bundles, the same pair of packages with two different entry files. Pick one before you start.
| Bundle | What’s in it | Gzip CSS + JS |
|---|---|---|
| Core | Every component except integrations (carousel, date-picker, command palette, etc.). | ~27 KB + ~27 KB |
| Full | Core plus every shipped integration in one bundle. One install line that gives you everything. | ~28 KB + ~37 KB |
If you’re not sure, pick core. Adding an integration later is just one extra import; you don’t need to swap the bundle.
CDN
No build step. Drop the bundles into a plain HTML page.
Core
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Stisla starter</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@stisla/css@3/dist/stisla.css">
</head>
<body>
<button type="button" class="btn btn--primary">Hello</button>
<script type="module" src="https://cdn.jsdelivr.net/npm/@stisla/vanilla@3/dist/stisla.js"></script>
</body>
</html>Full
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@stisla/css@3/dist/stisla-full.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@stisla/vanilla@3/dist/stisla-full.js"></script>The @3 tag follows the latest 3.x release. Pin to @3.0.0-beta.1 (or any future exact version) for reproducible builds.
npm
The same two bundles, installed and bundled by your tool of choice. Stisla ships two scoped packages. @stisla/css holds the universal stylesheet, and @stisla/vanilla is the JS runtime that drives it.
npm install @stisla/css @stisla/vanillaThe two packages share a version. @stisla/vanilla declares @stisla/css as a peer dependency pinned to the matching version, so if the pair doesn’t match you find out at install time instead of silently at runtime.
Core
import '@stisla/css';
import '@stisla/vanilla';Importing @stisla/vanilla registers every core component and walks the DOM on the next microtask. Mark up with the standard classes and data-stisla-* attributes, and the scanner does the rest. There’s no init() call to make.
Full
import '@stisla/css/full';
import '@stisla/vanilla/full';Core plus à la carte integrations
Start from core and add one extra import per integration you actually use. The bundle stays small for everything you skip.
import '@stisla/css';
import '@stisla/vanilla';
import '@stisla/css/integrations/carousel';
import '@stisla/vanilla/integrations/carousel';JS runtime
Importing @stisla/vanilla (or loading stisla.js via the CDN snippet above) does three things in one go. It registers every component class, exposes them on window.Stisla, and walks the document on the next microtask to wire up anything marked with a data-stisla-* attribute. There is no init() call to make at startup.
Declarative
The runtime is driven mostly from HTML. Mark a component root with data-stisla-<name>, give triggers and dismissers their matching attributes, and the scanner wires the behavior on first paint.
<button type="button" class="btn btn--primary" data-stisla-dialog-trigger="hello">
Open
</button>
<div class="dialog" id="hello" data-stisla-dialog data-state="closed" role="dialog" aria-modal="true" tabindex="-1">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__content">
<div class="dialog__body">Hello.</div>
</div>
</div>Every component page documents the attributes it understands. Options serialize as per-attribute kebab-case (data-stisla-dialog-close-on-backdrop="false"), or as a single JSON blob on data-stisla-opts for values that don’t fit cleanly in an attribute.
Programmatic
If you need a handle to open from JS, listen for events, or destroy on unmount, construct the class directly. Each component class is also a named export on the bundle.
const dialog = new Stisla.Dialog(document.getElementById('hello'));
dialog.show();
dialog.root.addEventListener('stisla:dialog:hidden', () => {
// ...
});Re-scanning
The initial scan runs once per page load. For dynamic content like SPA route changes, AJAX-injected fragments, or server-streamed HTML, call Stisla.init() against the affected subtree to wire up anything new. The scanner is idempotent, so elements that already have an instance are skipped.
// After fetching and inserting HTML into a container:
Stisla.init(document.getElementById('panel'));Sass source
The pre-compiled bundle is the recommended path. Most customization is a single CSS variable override at runtime, and you don’t need Sass for that. The Sass source ships inside @stisla/css for the harder cases. Forking stisla.scss to drop unused components, changing breakpoint sizes, or rewriting a component from the ground up.
@use '@stisla/css/scss/bundles/stisla';Or fork the bundle entry and comment out the components you don’t use. The Optimization page has the recipe and real before/after byte counts.
Browser support
Safari 16.4 or newer, Chrome 111 or newer, and Firefox 121 or newer. No polyfills. The features Stisla relies on (OKLCH, color-mix, :has(), @layer, container queries, inert) are stable across that range. If you need to support older browsers, falling back to an earlier version of the framework won’t help, because the runtime token surface is what makes Stisla what it is.
What’s next
Pick a primary color, walk the component pages, or read the introduction if you want the why before the how.