@sparkstone/css
v2.1.0
Published
A minimal CSS framework inspired by Pico.css, with OKLCH-powered theming, light/dark mode, and both class-based and classless builds.
Maintainers
Readme
@sparkstone/css
A minimal CSS framework inspired by Pico.css, rebuilt with oklch() color primitives and a themeable design system using native CSS custom properties. Ships two builds from one source: class-based (the main export) and classless.
🔗 View on NPM | View on GitHub
✨ Features
- 💡 Built with
oklch()for perceptually uniform color scales - 🌗 Automatic light/dark theming with CSS variables
- 🧱 Class-based build with short, semantic class names (
btn,input,card, ...) - 📄 Classless build that styles plain HTML (the 1.x theme)
- ⚡ No JS required for core styles
- 🎨 Fully themeable via CSS variables or Sass functions
- 🧩 SCSS mixins for advanced integrations
🚀 Install
# pnpm
pnpm add @sparkstone/css# yarn
yarn add @sparkstone/css# npm
npm install @sparkstone/css🌐 Live Demo & Docs
Explore the docs and theme live:
🔗 https://sparkstonepdx.github.io/css/docs
📦 Usage
Pick a build
// Class-based (main export)
@use "pkg:@sparkstone/css";
// Classless
@use "pkg:@sparkstone/css/classless";// Precompiled CSS
import '@sparkstone/css'; // dist/index.css
import '@sparkstone/css/classless'; // dist/classless.csspkg: URLs need Sass's Node package importer (--pkg-importer=node, or importers: [new NodePackageImporter()]). Vite resolves the plain imports on its own.
Class-based
Plain elements only get the reset and base colors. Styling comes from classes:
| Component | Classes |
| --- | --- |
| Button | btn, btn-primary, btn-secondary, btn-ghost, btn-neutral, btn-disabled |
| Form controls | input, select, textarea, checkbox, radio, range, file-input, color-input, input-error, select-error, textarea-error |
| Form layout | label, fieldset, fieldset-legend |
| Card | card, card-border, card-actions |
| Dialog | dialog, dialog-box, dialog-header, dialog-actions |
| Content | prose (styles p, headings, lists, blockquote, hr, code, pre, kbd inside it), link, kbd, table, progress, badge |
| Utilities | text-secondary, text-error, disabled, container, container-fluid, flex, flip, reverse, rounded |
<article class="card">
<h2>Hello World</h2>
<button class="btn btn-primary">Go</button>
</article>Alpha components
Tabs, Dropdown, Navbar, Breadcrumbs, Pagination, Alert, Collapse, Skeleton,
Toggle, Slider, Range, Segmented control, Tooltip, Popover, Toast, Divider and Loading
ship as alpha: their class
names and states are settled, but their styling can change in a minor release.
DESIGN-REVIEW.md lists every value that is still open, and the docs badge them.
Classless
<article>
<h2>Hello World</h2>
<button>Go</button>
</article>The classless build emits the same classes and binds them to elements with Sass @extend, which is the native equivalent of Tailwind's @apply:
button { @extend .btn; } // compiles to: .btn, button { ... }One rule serves both builds, so they cannot drift apart, and the classless build carries the class names too if you want to mix the two. When changing styles, edit the class in src/components/, not the entry files.
Migrating from 1.x
1.x's theme is now the classless build. Replace @sparkstone/css/src/theme.scss with @sparkstone/css/src/classless.scss (or pkg:@sparkstone/css/classless), and dist/theme.css with dist/classless.css. Importing the package root now gives you the class-based build.
Two rendering changes come with it:
input[type="submit"],[type="reset"]and[type="button"]are styled only as buttons. In 1.x they also picked up the text-field rules, so they stretched to the full width and carried a bottom margin..cardis the base card in both builds. Rename 1.x's.cardto.card-borderfor the bordered look.
🎨 Theming
Set custom colors using CSS variables:
:root {
--color: rebeccapurple;
--primary-color: blue;
--accent-color: oklch(from var(--color) l c calc(h + 180));
--error-color: maroon;
}System-based dark mode is supported by default, but you can override manually:
<html data-color-scheme="light">
<!-- or -->
<html data-color-scheme="dark"></html>
</html>🧑🎨 Theme Swatches
Use these CSS variables for consistent contrast:
--text-lc-1...--text-lc-9--surface-lc-1...--surface-lc-9
They adjust automatically in dark/light mode and derive from --color.
You can preview or override them using:
@use '@sparkstone/css/src/vars.scss' as *;
// Example: generate a color
color: get-color(var(--text-lc-2), var(--accent-color));
border-color: get-border-color();🧪 Documentation
See it live via GitHub Pages:
- Overview: what the two builds are, and how they relate
- Quickstart: install, pick a build, set a color
- Colors: the scale, with an interactive color picker
- Customizing: tokens, scoping, and Sass entry points
- One page per component, from Button to Dialog
Every example has a Class and a Classless tab. The preview renders in a frame loading the matching build, so what you see is what that build produces.
🛠 Dev
pnpm install
pnpm devThis watches both src/ and pages/ for changes. It compiles SCSS to dist/, renders Nunjucks templates from pages/ to docs/, and serves with live reload.
To build manually:
pnpm build📦 Package Structure
dist/ # Compiled CSS (index.css, classless.css)
src/index.scss # Class-based entry
src/classless.scss # Classless entry
src/components/ # One mixin per component, shared by both entries
src/vars.scss # Color scale, functions
pages/ # Nunjucks page templates
templates/ # Shared macros and layout
docs/ # Output static site for GitHub Pages💬 License
MIT © Sparkstone LLC
