Skip to main content
Casca
Theme

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 PhilosophyLink to section

Casca is optimized for server-rendered, no-JS-first dashboards and prioritizes:

  1. Zero global side effects - No :root{color-scheme}, no --lightningcss-* build artifacts, all custom properties use --casca-* prefix
  2. NO-JS architecture - All core features work without JavaScript (pagination, interactivity, theming)
  3. Minimal, composable primitives - Small, focused components that work together
  4. 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.

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:

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:

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-onAdds
casca-extended-charts.cssheatmap, scatter/bubble, waterfall, radar, candlestick
casca-extended-controls.cssslot grid, range slider, and the no-JS interaction primitives (toggle, switch, disclosure, filter)
casca-extended-layout.cssthe 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 -->
OverlayPaletteUse when
casca-theme-default.cssThe default categorical paletteThe standard look (already baked into casca.css)
casca-theme-high-contrast.cssDark, 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.cssLow-chroma, muted, harmonious tones with a barely-there gridUnderstated, professional dashboards
casca-theme-vibrant.cssBold, high-chroma, energetic colorsConsumer-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:

Safe to add (opt-in, never required):

Refused (would break the contract):

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:

General Integration PatternLink to section

  1. Install Casca:

    npm install @skellvin/casca
    # or
    bun add @skellvin/casca
    
  2. Import CSS (framework-specific syntax):

    // Astro, Next.js, etc.
    import '@skellvin/casca/core';
    // or
    import '@skellvin/casca';
    
  3. 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>
    
  4. (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

❌ Real-time Animated ChartsLink to section

❌ Very Complex VisualizationsLink to section

❌ Legacy Browser SupportLink to section

✅ Use Casca When You Need:Link to section


Getting HelpLink to section


LicenseLink to section

See LICENSE file in project root.