Casca Adoption Guide
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 Contents
- Design Philosophy
- Which Build Should I Use?
- Contract Guarantees
- Generalization Policy
- Bring Your Own Theme
- Framework Integration
- When NOT to Use Casca
Design Philosophy
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?
Casca ships one CSS artifact: dist/casca.css. It contains every primitive plus
the four-mood theme picker (Solar base + Cyber/Lunar/Arcade picker-scoped overlays).
dist/casca.css (The one bundle)
<link rel="stylesheet" href="casca.css">
This is the recommended starting point for everyone. It is contract-clean
(no :root{color-scheme}, no globals) and gives you every chart, every
interaction, every layout shell, and the four-mood picker in one link tag.
Tree-shake recipe (subset users)
If you need a curated subset, write your own entry CSS with explicit @import
statements for the domain aggregators you want:
/* my-casca.css -- charts-only entry */
@layer casca.design-tokens, casca.tokens, casca.base, casca.charts;
@import "@skellvin/casca-css/src/core/_index.css"; /* required base */
@import "@skellvin/casca-css/src/charts/_index.css"; /* all 18 chart primitives */
Bundle with lightningcss:
lightningcss --bundle --minify \
--targets 'chrome 95, firefox 95, safari 15' \
--output-file my-casca.css \
my-casca.css
Domain aggregators:
| Domain | Import path | Contains |
|---|---|---|
| Core (required) | src/core/_index.css | Design tokens, base primitives, accessibility, prose, keyboard shortcuts |
| Charts | src/charts/_index.css | All 18 chart primitives (axis, bar, line, area, pie, progress, stat, table, analytics, heatmap, scatter, waterfall, radar, candlestick, slot-grid, range, date, color) |
| Interactions | src/interactions/_index.css | No-JS form-aware primitives (toggle, switch, disclosure, filter, segment, modal) |
| Layout | src/layout/_index.css | Page/dashboard shell (card, card-grid, toolbar, anchor-nav, site-header/footer, theme-picker, hero) |
| Themes | src/themes/_index.css | All four mood overlays (for tree-shake consumers; not imported by dist/casca.css) |
Core MUST be imported first (it defines --casca-* variables and the .casca container).
All other domains depend on core.
// package exports
import "@skellvin/casca"; // dist/casca.css (the one bundle)
import "@skellvin/casca/live"; // dist/casca-live.js (opt-in live layer)
import "@skellvin/casca/src/*"; // src/* domain aggregators for tree-shaking
Overriding the mood tokens on casca.css
casca.css 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:
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.
Theming from scratch? Use the tree-shake recipe with src/core/_index.css
(no bundled mood layer to fight) and define your --casca-* tokens once. It is
cleaner than overriding casca.css, especially around dark mode and
picker-scoped mood changes.
Contract Guarantees
Casca enforces a strict CSS contract via automated linting:
What Casca Guarantees
✅ 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 Guarantee
❌ Backwards compatibility with ancient browsers - Requires modern CSS (see Compatibility) ❌ Framework-specific integrations - Pure CSS; you handle framework bindings ❌ Interactive charts without server round-trips - Use forms/links; add JS if needed
Enforced by Linter
The contract is automatically verified:
make lint # Lints dist/casca.css against the Casca contract
make lint must pass before any release.
Generalization Policy
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.cssis the single CSS artifact. Host apps that need a subset use the tree-shake recipe (src/<domain>/_index.cssaggregators).- All published bundles pass
make lint(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 inside
dist/casca.cssvia thecasca.themeslayer. 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 Theme
If you're using Casca, you can provide CSS custom properties for theming.
Minimal Theme Bridge Example
/* 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)
The form-aware slot grid (casca-slot-grid) is a native radio group, so it
works from casca.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 Integration
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 Pattern
-
Install Casca:
npm install @skellvin/casca # or bun add @skellvin/casca -
Import CSS (framework-specific syntax):
// Astro, Next.js, etc. import '@skellvin/casca'; -
Render semantic HTML:
<figure class="casca casca-figure"> <figcaption class="casca-title">Sales</figcaption> <table class="casca-data"> <caption>Sales</caption> <thead><tr><th scope="col">Period</th><th scope="col">Value</th></tr></thead> <tbody><tr><th scope="row">Latest</th><td>75</td></tr></tbody> </table> <div class="casca-bar" aria-hidden="true"> <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> </figure> -
(Optional) Add theme bridge to map Casca tokens to your design system
When NOT to Use Casca
Casca is not a good fit if you need:
❌ Heavy JavaScript Interactivity
- Casca: NO-JS first, forms/links for state changes
- Alternative: Chart.js, D3.js, Recharts
❌ Real-time Animated Charts
- Casca: CSS transitions only, no frame-by-frame animation
- Alternative: Chart.js, ApexCharts
❌ Very Complex Visualizations
- Casca: Bar, pie, line, area, gauge, progress (simple charts)
- Alternative: D3.js, Plotly, Observable Plot
❌ Legacy Browser Support
- Casca: Modern CSS (
:has(), container queries,@property) - Alternative: Chart.js (uses
<canvas>)
✅ Use Casca When You Need:
- 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 Help
- Documentation: API Reference
- Examples: See
site/pages/directory - Integration Guides: See
docs/integrations/ - Integration Guide: Integration
- Issues: File bugs/feature requests at the project repository
License
See LICENSE file in project root.
Linked references
- Cascalink
- linked as Adoption
- Integration Guide (server-rendered, no-JS first)link
- linked as Adoption