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

index.html
<!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/vanilla

The 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.

your-app.scss
@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.