Optimization
Three ways to shrink the shipped CSS. Purge against rendered HTML, fork the bundle and drop components, or change the Sass knobs at the entry point.
Starting point
The pre-compiled core bundle is the right default for most apps. The numbers below are what @stisla/css@3 ships as of beta.1.
| Bundle | Raw | Gzip |
|---|---|---|
stisla.css (core, 41 components) |
~201 KB | ~27 KB |
stisla-full.css (core + carousel) |
~205 KB | ~28 KB |
stisla.js (core runtime) |
~99 KB | ~27 KB |
27 KB of gzipped CSS is small enough that most apps don’t need to optimize at all. If yours does, here are three levers in order of effort.
Lever 1. Purge against rendered HTML
This is the cheapest win. A typical app uses 30 to 40 percent of the BEM classes Stisla ships. PurgeCSS or LightningCSS scans your rendered output, drops the selectors you never reference, and leaves the rest of the cascade intact.
PurgeCSS
import { PurgeCSS } from 'purgecss';
const result = await new PurgeCSS().purge({
content: ['dist/**/*.html', 'src/**/*.{js,ts,jsx,tsx,vue}'],
css: ['node_modules/@stisla/css/dist/stisla.css'],
safelist: {
standard: [/^is-/, /^data-state/, /^data-theme/],
deep: [/^sidebar__/, /^dialog__/, /^drawer__/],
},
});
await Bun.write('dist/stisla.purged.css', result[0].css);The safelist matters. Stisla relies on state classes (.is-loading, .is-active) and BEM children (.dialog__title, .sidebar__menu) that the JS adds at runtime. PurgeCSS only sees what’s in your HTML and templates, so any class the runtime injects needs to be allow-listed by pattern.
LightningCSS
If you’re already on a Vite or Lightning-based pipeline, the same idea applies with different syntax. LightningCSS does the scanning and minification in one pass.
import { defineConfig } from 'vite';
export default defineConfig({
css: {
transformer: 'lightningcss',
lightningcss: {
drafts: { customMedia: true },
// LightningCSS doesn't purge on its own; pair with a content
// scanner (purgecss-from-html / cssnano / custom plugin).
},
},
});Measure before and after. The win depends on how many components your app touches. A dashboard that uses overlays, tables, and forms might purge down to 50 KB raw. A landing page that uses one button and one card might purge down to 20 KB raw.
Lever 2. Fork the bundle
If you know up front which components you don’t use, dropping them at Sass compile time is faster than scanning rendered HTML and gives reproducible numbers.
Copy stisla.scss into your project as stisla.custom.scss and comment out the components you don’t need. The bundle file is the manifest, so deleting a line removes that component from the build.
@layer foundation, theme, components, utilities;
@import '@stisla/css/scss/tokens/breakpoints';
@import '@stisla/css/scss/foundation/mixins';
@layer foundation {
@import '@stisla/css/scss/foundation/normalize';
@import '@stisla/css/scss/foundation/reboot';
@import '@stisla/css/scss/foundation/typography';
@import '@stisla/css/scss/foundation/grid';
@import '@stisla/css/scss/foundation/containers';
}
@layer theme {
@import '@stisla/css/scss/tokens/theme';
}
@layer components {
// Forms.
@import '@stisla/css/scss/components/input';
@import '@stisla/css/scss/components/form-label';
@import '@stisla/css/scss/components/btn';
// Surfaces.
@import '@stisla/css/scss/components/card';
@import '@stisla/css/scss/components/badge';
@import '@stisla/css/scss/components/alert';
@import '@stisla/css/scss/components/link';
// Drop everything else for this build.
// @import '@stisla/css/scss/components/dialog';
// @import '@stisla/css/scss/components/drawer';
// @import '@stisla/css/scss/components/dropdown';
// ...
}
@layer utilities {
@import '@stisla/css/scss/utilities/utilities';
}The example above keeps seven components (button, input, form label, card, badge, alert, link), which is the kind of subset you’d use on a landing page or marketing site.
| Bundle | Raw | Gzip |
|---|---|---|
| Default core (41 components) | ~201 KB | ~27 KB |
| Forked bundle (7 components) | ~137 KB | ~14 KB |
| Saved | ~64 KB | ~13 KB |
Half the gzipped weight gone for a small app. Most of what’s left is the foundation (reset, reboot, typography, grid, the theme token block). That stays regardless of which components you keep, and it’s what makes the runtime overrides possible.
Pair this with Lever 1 if you want both. Fork to remove what you know you don’t need, then purge to drop the BEM children you didn’t reach inside the components you kept.
Lever 3. Change Sass-level knobs
A handful of decisions have to live in Sass. Breakpoints are the main one. Media queries can’t read CSS variables, so the breakpoint surface is a Sass map. Forking stisla.scss lets you override that map before the foundation compiles.
// Override before the breakpoints partial loads.
$breakpoint-sm: 640px;
$breakpoint-md: 768px;
$breakpoint-lg: 1024px;
$breakpoint-xl: 1280px;
$breakpoint-xxl: 1536px;
@layer foundation, theme, components, utilities;
@import '@stisla/css/scss/tokens/breakpoints';
@import '@stisla/css/scss/foundation/mixins';
// ...rest of the bundle as shipped.The grid, container queries, and every @include media-up() in the codebase pick up the new values. The runtime CSS variable surface is untouched.
This is also the path for changing token defaults at build time rather than overriding them at runtime. Set --st-radius in tokens/theme and the entire system rebuilds against the new value, baking it into the cascade instead of mixing it from a custom property. It’s useful if you’re shipping a single locked theme and want the smallest possible token table.
Which lever, when
- Lever 1 if you don’t know which components you use. Most teams don’t. The scanner figures it out and you ship what’s reached.
- Lever 2 if you do. Faster than scanning, reproducible across builds, and it lets you delete component code from your dependency tree instead of from the output bundle.
- Lever 3 if breakpoints or tokens are wrong for you. The defaults work for most apps. This is the escape hatch for the ones they don’t.
Most apps need at most one of these. If you find yourself reaching for all three, you might want a different framework. Stisla is a design specification, and it isn’t trying to be a meta-toolkit.