Casca Adoption GuideLink to section
Casca is a no-JS-first CSS library of charts and interactive data components, designed for server-rendered dashboards. This guide explains how to adopt Casca in your project while understanding its design philosophy and guarantees.
Table of ContentsLink to section
- Design Philosophy
- Which Build Should I Use?
- Contract Guarantees
- Generalization Policy
- Bring Your Own Theme
- Framework Integration
- When NOT to Use Casca
Design PhilosophyLink to section
Casca is optimized for server-rendered, no-JS-first dashboards and prioritizes:
- Zero global side effects - No
:root{color-scheme}, no--lightningcss-*build artifacts, all custom properties use--casca-*prefix - NO-JS architecture - All core features work without JavaScript (pagination, interactivity, theming)
- Minimal, composable primitives - Small, focused components that work together
- Predictable theming via CSS custom properties - Easy to override, hard to break
If your project shares these values, Casca is a great fit. If you need heavy JS interactivity or complex animations, consider a different library.
Which Build Should I Use?Link to section
Casca splits along two axes - pick a build (which components are included), then a theme overlay (the palette). All bundles are contract-clean.
dist/casca-core.css (Recommended for host apps with their own design system)Link to section
The lean essentials - zero global side effects, bring your own theme. Base
plus bar, line, area, pie/donut, progress/gauge, KPI stat cards, and the data
table. Add casca-extended-charts / casca-extended-controls for the rest.
<link rel="stylesheet" href="casca-core.css">
Use when:
- You have an existing design system with its own tokens
- You want full control over theming
- You need strict "zero globals" guarantees (e.g., embedding in a host app)
- You're integrating into an existing server-rendered dashboard or host app
Theme bridge pattern:
/* your-theme.css */
.casca {
--casca-color-1: var(--your-brand-primary);
--casca-color-2: var(--your-brand-secondary);
--casca-gray-0: var(--your-bg-color);
--casca-gray-8: var(--your-text-color);
/* ... map remaining tokens */
}
dist/casca.css (All-in-one convenience bundle)Link to section
Core + every extension + the four-mood picker bundle - one <link>, every mood. Still contract-clean.
<link rel="stylesheet" href="casca.css">
Use when:
- You're prototyping or don't have a design system yet
- You want Solar by default, with Cyber, Lunar, and Arcade available through the no-JS picker
- You still want zero global side effects (theme is
.cascascoped)
Without .casca-theme-picker[data-casca-theme-picker] markup or
body[data-casca-theme], Solar renders as the unscoped base and normal pages
keep the old default-theme behavior.
Note: Even casca.css is contract-clean (no :root{color-scheme}, no globals). It's safe to include in any project.
Extensions (extended-charts, extended-controls, extended-layout)Link to section
Three opt-in add-ons that layer on top of casca-core.css (load them alongside
core, or use the all-in-one casca.css):
<link rel="stylesheet" href="casca-core.css">
<link rel="stylesheet" href="casca-extended-charts.css"> <!-- + advanced charts -->
<link rel="stylesheet" href="casca-extended-controls.css"> <!-- + interactive controls -->
<link rel="stylesheet" href="casca-extended-layout.css"> <!-- + page/dashboard shell -->
| Add-on | Adds |
|---|---|
casca-extended-charts.css | heatmap, scatter/bubble, waterfall, radar, candlestick |
casca-extended-controls.css | slot grid, range slider, and the no-JS interaction primitives (toggle, switch, disclosure, filter) |
casca-extended-layout.css | the page/dashboard shell: card, card-grid, toolbar, anchor-nav (tabs are .casca-switch[data-variant="tabs"]) |
They carry no base/theme of their own, so they are add-ons - not standalone
bundles. The components are byte-for-byte the same rules as in casca.css.
Theme overlays (theme-default, theme-high-contrast, theme-muted, theme-vibrant)Link to section
A theme is just a tiny (~1 KB) block of --casca-* palette values. Load one
over casca-core.css (or over any build, to override its palette):
<link rel="stylesheet" href="casca-core.css">
<link rel="stylesheet" href="casca-theme-high-contrast.css"> <!-- default / muted / vibrant -->
| Overlay | Palette | Use when |
|---|---|---|
casca-theme-default.css | The default categorical palette | The standard look (already baked into casca.css) |
casca-theme-high-contrast.css | Dark, saturated hues with ≥4.5:1 contrast on white (bright counterpart in dark mode) | You want a stronger, more accessible palette by choice (distinct from the OS-driven forced-colors handling every chart already ships) |
casca-theme-muted.css | Low-chroma, muted, harmonious tones with a barely-there grid | Understated, professional dashboards |
casca-theme-vibrant.css | Bold, high-chroma, energetic colors | Consumer-facing dashboards (aesthetic, not contrast-guaranteed) |
// package exports
import "@skellvin/casca"; // dist/casca.css (four-mood all-in-one)
import "@skellvin/casca/portal"; // dist/casca-portal.css (deprecated one-release alias)
import "@skellvin/casca/core"; // dist/casca-core.css (lean, bring your own theme)
import "@skellvin/casca/extended-charts"; // dist/casca-extended-charts.css
import "@skellvin/casca/extended-controls"; // dist/casca-extended-controls.css
import "@skellvin/casca/extended-layout"; // dist/casca-extended-layout.css
import "@skellvin/casca/theme-default"; // dist/casca-theme-default.css
import "@skellvin/casca/theme-high-contrast"; // dist/casca-theme-high-contrast.css
import "@skellvin/casca/theme-muted"; // dist/casca-theme-muted.css
import "@skellvin/casca/theme-vibrant"; // dist/casca-theme-vibrant.css
Each theme only remaps --casca-* tokens (no host globals), so swapping the
overlay reskins every chart. Host apps that map their own tokens just use
casca-core.css and skip the theme. See site/pages/themes/high-contrast.html,
site/pages/themes/muted.html, and site/pages/themes/vibrant.html.
Overriding the mood tokens on casca.cssLink to section
casca.css already carries Solar as the unscoped base plus Cyber, Lunar, and
Arcade as picker-scoped overlays. All of those mood values live in the
casca.themes cascade layer (the last Casca layer), which makes it easy to
override two ways:
1. Force a stock standalone theme. Load a casca-theme-*.css overlay
after casca.css. Both live in the same casca.themes layer, so the later
<link> wins for normal pages without picker markup:
<link rel="stylesheet" href="casca.css">
<link rel="stylesheet" href="casca-theme-vibrant.css"> <!-- overrides the Solar base -->
2. Bring your own palette. Define your own --casca-* values in an
unlayered rule. Unlayered styles beat every @layer, so they win over the
bundled moods with no !important:
<link rel="stylesheet" href="casca.css">
<style>
:root {
--casca-color-1: #6741d9; /* your brand accent */
--casca-color-2: #0ca678;
/* ...any other --casca-* token you want to remap... */
}
</style>
You can also set a variable inline on a single .casca block
(style="--casca-color-1: #6741d9") to reskin just that one chart.
Dark-mode caveat. The bundled moods remap colors inside a
@media (prefers-color-scheme: dark)block. An unlayered light-mode override (with no media query) also applies in dark mode, so it replaces that remap. If you want a distinct dark palette, set it yourself:@media (prefers-color-scheme: dark) { :root { --casca-color-1: #9775fa; } }
Theming from scratch? Prefer casca-core.css. If you are not keeping any of
the stock moods, start from casca-core.css (no bundled mood layer to fight)
and define your --casca-* once. It is cleaner than overriding casca.css,
especially around dark mode and picker-scoped mood changes.
Contract GuaranteesLink to section
Casca enforces a strict CSS contract via automated linting:
What Casca GuaranteesLink to section
✅ No unscoped global selectors - All styles are scoped to .casca or child classes
✅ No :root{color-scheme} pollution - Themes use prefers-color-scheme media queries instead
✅ All :root variables use --casca-* prefix - No naming conflicts with host styles
✅ No build-injected globals - No --lightningcss-* or other processor artifacts
✅ No JavaScript required - All features work with pure HTML+CSS
What Casca Does NOT GuaranteeLink to section
❌ Backwards compatibility with ancient browsers - Requires modern CSS (see COMPATIBILITY.md) ❌ Framework-specific integrations - Pure CSS; you handle framework bindings ❌ Interactive charts without server round-trips - Use forms/links; add JS if needed
Enforced by LinterLink to section
The contract is automatically verified:
bun run lint # Checks dist/casca-core.css
bun run lint:full # Checks dist/casca.css
bun run lint:extended-charts # Checks dist/casca-extended-charts.css
bun run lint:extended-controls # Checks dist/casca-extended-controls.css
bun run lint:extended-layout # Checks dist/casca-extended-layout.css
bun run lint:theme-default # Checks dist/casca-theme-default.css (+ -high-contrast / -muted / -vibrant)
bun run lint:all # Runs every check above
All bundles must pass lint:all before any release. See scripts/lint-css.js for implementation.
Generalization PolicyLink to section
Casca is no-JS-first, and it is meant to be safely adoptable by any host app. This policy says what the project will add, what it will refuse, and what stays fixed - so growing the audience never erodes the core guarantees.
Stays true no matter what:
dist/casca-core.cssremains the recommended artifact for any host app that maps its own tokens.- All published bundles pass
bun run lint:all(zero global side effects;:rootcarries only--casca-*; no:root{color-scheme}). - Core features require no JavaScript.
Safe to add (opt-in, never required):
- Additional scoped themes shipped as their own contract-clean overlays
(e.g.
casca-theme-high-contrast.css). Themes only remap--casca-*tokens and never set host-page globals. - Docs-only framework examples (Astro, Eleventy, Hugo, Django, Phoenix, server components) - pure HTML + CSS, no runtime dependency.
- Markup templates / snippets for common patterns (Go templates under
site/assets/integrations/go-templates/), not runtime adapters.
Refused (would break the contract):
- JavaScript as a requirement for any core behavior. Optional helpers, if ever added, ship separately and host apps must never need them.
- "Just this once" global resets or host-page side effects.
- New primitives without docs (
docs/API.md), an example, and passing lint on every bundle.
Every new primitive or public token is API: document it, add an example, and keep all three lint commands green.
Bring Your Own ThemeLink to section
If you're using casca-core.css, you'll need to provide CSS custom properties for theming.
Minimal Theme Bridge ExampleLink to section
/* Map Casca tokens to your design system */
.casca {
/* Brand colors */
--casca-color-1: var(--brand-primary, #228be6);
--casca-color-2: var(--brand-secondary, #12b886);
--casca-color-3: var(--brand-accent, #fd7e14);
--casca-color-4: var(--brand-highlight, #e64980);
--casca-color-5: var(--success-color, #40c057);
--casca-color-6: var(--info-color, #845ef7);
--casca-color-7: var(--warning-color, #fab005);
--casca-color-8: var(--danger-color, #fa5252);
/* Grayscale (light mode) */
--casca-gray-0: #ffffff;
--casca-gray-1: #f8f9fa;
--casca-gray-2: #e9ecef;
--casca-gray-3: #dee2e6;
--casca-gray-4: #ced4da;
--casca-gray-5: #adb5bd;
--casca-gray-6: #6c757d;
--casca-gray-7: #495057;
--casca-gray-8: #343a40;
--casca-gray-9: #212529;
/* Optional: spacing/sizing if you want to override defaults */
--casca-size-1: 0.25rem;
--casca-size-2: 0.5rem;
--casca-size-3: 0.75rem;
--casca-size-4: 1rem;
--casca-size-5: 1.5rem;
--casca-size-6: 2rem;
}
/* Dark mode overrides */
@media (prefers-color-scheme: dark) {
.casca {
--casca-gray-0: #1a1a1a;
--casca-gray-1: #2c2c2c;
--casca-gray-2: #3a3a3a;
--casca-gray-3: #4a4a4a;
--casca-gray-4: #5a5a5a;
--casca-gray-5: #6a6a6a;
--casca-gray-6: #a0a0a0;
--casca-gray-7: #c0c0c0;
--casca-gray-8: #e0e0e0;
--casca-gray-9: #f5f5f5;
}
}
See src/themes/default.css for the complete default theme as a reference.
Slot Grid (booking / scheduling UIs)Link to section
The form-aware slot grid (casca-slot-grid) is a native radio group, so it
works from casca-core.css with no theme dependency - it falls back to the
casca grayscale + --casca-color-1 and is dark-mode aware out of the box.
To match it to your design system, map its --casca-slot-* tokens (all
optional; sensible defaults are built in):
/* Map the slot grid to your booking UI */
.casca-slot-grid {
/* Selected slot = your primary action color */
--casca-slot-selected-bg: var(--brand-primary, #228be6);
--casca-slot-selected-color: #ffffff;
--casca-slot-selected-border: var(--brand-primary, #228be6);
/* Available + hover surfaces */
--casca-slot-bg: var(--surface-2, #f8f9fa);
--casca-slot-border: var(--border-subtle, #dee2e6);
--casca-slot-hover-bg: var(--surface-3, #e9ecef);
/* Taken / disabled */
--casca-slot-taken-color: var(--text-muted, #adb5bd);
/* Layout knobs */
--casca-slot-cols: 5; /* or set inline per grid via style="" */
--casca-slot-min-size: 2.75rem; /* >= 44px touch target */
}
Because each slot is a real <input type="radio">, the chosen slot is submitted
with a normal <form> POST and is keyboard accessible without JavaScript. Always
re-validate the submitted slot server-side; disabled (taken) slots are never
submitted by the browser, but never trust that alone.
See site/pages/controls/slot-grid.html and site/assets/integrations/go-templates/slot-grid.tmpl.
Framework IntegrationLink to section
Casca is pure CSS and works with any framework that renders HTML. See docs/integrations/ for specific examples:
- Astro - Static site generation
- Hugo (coming soon)
General Integration PatternLink to section
-
Install Casca:
npm install @skellvin/casca # or bun add @skellvin/casca -
Import CSS (framework-specific syntax):
// Astro, Next.js, etc. import '@skellvin/casca/core'; // or import '@skellvin/casca'; -
Render semantic HTML:
<div class="casca casca-figure"> <div class="casca-title">Sales</div> <div class="casca-bar"> <div class="casca-bar-group"> <div class="casca-bar-item"> <div class="casca-bar-value" style="--value: 75%; --color: var(--casca-color-1)"></div> </div> </div> </div> </div> -
(Optional) Add theme bridge if using
casca-core.css
When NOT to Use CascaLink to section
Casca is not a good fit if you need:
❌ Heavy JavaScript InteractivityLink to section
- Casca: NO-JS first, forms/links for state changes
- Alternative: Chart.js, D3.js, Recharts
❌ Real-time Animated ChartsLink to section
- Casca: CSS transitions only, no frame-by-frame animation
- Alternative: Chart.js, ApexCharts
❌ Very Complex VisualizationsLink to section
- Casca: Bar, pie, line, area, gauge, progress (simple charts)
- Alternative: D3.js, Plotly, Observable Plot
❌ Legacy Browser SupportLink to section
- Casca: Modern CSS (
:has(), container queries,@property) - Alternative: Chart.js (uses
<canvas>)
✅ Use Casca When You Need:Link to section
- Server-rendered charts (SSR/SSG friendly)
- NO-JS or progressive enhancement
- Semantic HTML (accessible by default)
- Zero global side effects
- Minimal bundle size (44KB CSS, 0KB JS)
- Tight integration with existing CSS design systems
Getting HelpLink to section
- Documentation: API Reference
- Examples: See
site/pages/directory - Integration Guides: See
docs/integrations/ - Integration Guide: INTEGRATION.md
- Issues: File bugs/feature requests at the project repository
LicenseLink to section
See LICENSE file in project root.