Design philosophy
Three principles drive every shape decision in Krysalicss: strict modularity, runtime-switchable themes, and zero JavaScript in the framework output. Each carries a tradeoff worth being honest about.
Why JS-free
A CSS framework that ships JavaScript becomes coupled to the consumer's runtime. It must pick a framework adapter, version-pin against a peer dependency, and explain to every user why their bundle grew. Krysalicss avoids the problem by refusing the premise: the framework's published artefact is plain CSS, and every component is built from selectors available in evergreen browsers.
The cost is real. Components that genuinely need state: autocompletes,
sortable tables: are out of scope. Where a clean HTML
pattern exists (<dialog>, <details>, radio-input tabs,
:focus-within dropdowns) we'll use it; where one doesn't, we won't
paper over it with JS we then have to maintain. Modal and tabs ship
only because <dialog> and the radio-input pattern give us viable JS-free
implementations.
The docs site you're reading is a separate consumer of the framework. It
uses Astro islands (Vue 3) for the theme switcher and copy-to-clipboard
button because those are actual interactive behaviours. That JavaScript
does not ship as part of @adnap/krysalicss; it ships as part of the docs
site. The principle is about the framework's surface, not about whether
its homepage is hydrated.
Why opt-in
Most CSS frameworks make a tradeoff at install time: take everything, even the components you'll never use. The reset costs 2 KB, the grid 4, the typography another 3, and you ship all of it because the build pipeline can't see which selectors are reachable from your markup.
Krysalicss makes the tradeoff at compile time. Every component is a
self-contained SCSS plugin gated by a feature flag, and the entry file
propagates a single $feature-list down to every gate. A consumer who
needs only the button and the flex grid compiles a stylesheet containing
only those rules: not because a tree-shaker removed the rest, but because
the rest never compiled.
The cost is build complexity. You need a SCSS pipeline; npx sass counts,
but you can't drop a <link> tag and call it done unless you accept the
full bundle. We've judged this an acceptable cost for the audience:
SCSS-comfortable teams already have a Sass step; teams that don't can use
the pre-built CSS and accept the larger payload.
Why CSS custom properties for theming
Themes could be implemented at compile time: generate a separate stylesheet per theme, swap them at runtime. That works and produces smaller per-theme bundles. It also makes theme switching a network roundtrip, prevents per-element theme overrides, and forces the docs site to ship a stylesheet selector to switch themes without a reload.
CSS custom properties solve all three. A theme is a block of --kc-*
declarations; switching themes is a class toggle on <html>; per-element
overrides work via the cascade. Component plugins read tokens through the
defaultProperty mixin, which emits both a hardcoded fallback and a
var(--kc-token, fallback) reference, so unthemed contexts still render
correctly.
The cost is bundle size. Both light and dark themes ship together by default, because the win: instant theme switching, no FOUC, per-element overrides: is more valuable than the kilobytes saved by lazy-loading. Consumers who need only one theme can use the forwards-only entry and skip the bundled themes entirely.
What we won't pretend
Krysalicss is not a Tailwind alternative. The audience is component-first, not utility-first; teams that want hundreds of generated atomic classes are better served elsewhere.
It is not a Bootstrap alternative either. Bootstrap ships JavaScript for its interactive components, and the breadth of those components is its selling point. Krysalicss ships fewer components on purpose; the discipline is what keeps the framework small enough to read top to bottom in an afternoon.
It is most directly a successor to Bulma and Picnic.css: both SCSS-native, both component-first, both opinionated. Krysalicss extends that lineage with a stricter opt-in story, first-class multi-theme support, and modern Sass module semantics. The comparison page goes further on where it sits.