Skip to main content
Casca
Theme

Browser CompatibilityLink to section

Casca targets evergreen modern browsers and uses cutting-edge CSS features for maximum simplicity and performance.

Browser Support MatrixLink to section

✅ Fully Supported (All Features)Link to section

BrowserMinimum VersionRelease DateNotes
Chrome105+Aug 2022Container queries, :has()
Edge105+Sep 2022Chromium-based
Safari16.0+Sep 2022:has() support
Firefox121+Dec 2023:has() support (latest requirement)
Opera91+Oct 2022Chromium-based

The matrix above covers the core CSS feature set. The optional <casca-layout> Declarative Shadow DOM chrome is a Baseline 2024 feature with a higher floor (Chrome 111+, Safari 16.4+, Firefox 123+); see the Partial Support row below and the <casca-layout> Declarative Shadow DOM section for the fallback behavior on older engines.

⚠️ Partial Support (Fallbacks Active)Link to section

Older browsers may render charts but with degraded features:

BrowserVersionLimitationFallback Behavior
Safari15.4-15.9No :has() for legend paginationStatic page indicator ("Pages: 1-3")
Firefox110-120No :has() for legend paginationStatic page indicator
Chrome88-104No container queriesFixed chart sizing
Chrome 110 and older / Safari 16.3 and older / Firefox 122 and olderNo Declarative Shadow DOM for <casca-layout>The <template shadowrootmode> stays an inert template; slotted light-DOM content renders directly without the shadow chrome
Chrome105-110 / Safari 16.0-16.1No color-mix() for heatmap intensity or the table row-selection tintHeatmap: opacity (approximate); selected rows: no tint, but the checked checkbox still conveys selection
Safari15.0-15.3No accent-color for the range sliderSlider renders with the default system accent (still functional, just not brand-colored)

❌ Not SupportedLink to section

Optional live layer (dist/casca-live.js)Link to section

The opt-in dist/casca-live.js requires Chrome 66+, Edge 16+, Firefox 57+, Safari 12.1+ (for fetch + AbortController). Older browsers ignore the script and render the static analytics block only; the no-JS baseline is unchanged. The asset is NOT loaded by default; hosts include it with an explicit <script> tag.


Required CSS FeaturesLink to section

Casca relies on these modern CSS features:

The build pipeline also uses lightningcss 1.0.0-alpha.71 through both the pinned CLI and the casca Rust binary. casca build uses the same version, with default targets of chrome 95, firefox 95, safari 15. The parser and printer must support :has(), @layer, @media, @supports, @container, custom properties, and custom media parsing because casca scope-theme validates and scopes source theme overlays before writing dist/casca.css. Passing custom casca build --targets values changes lightningcss fallback generation and may change the generated CSS.

.casca-modal must not be nested inside an ancestor that creates a stacking context. The modal backdrop and dialog box use fixed positioning with var(--casca-z-modal), so placing the wrapper under an isolated card, transformed panel, or z-indexed positioned ancestor can trap the overlay below other page chrome.

casca check-modal <FILE> enforces this contract for .html and .htm source by walking modal ancestors. It detects inline ancestor styles for: non-static position with non-auto z-index, transform, filter, opacity below 1, isolation: isolate, non-normal mix-blend-mode, stacking-affecting will-change, and contain values of layout, paint, strict, or content. It also knows the current Casca-owned blocker classes .casca-card, .casca-site-header, and .casca-modal-box.

For .css input, casca check-modal <FILE> is intentionally narrow. It checks direct modal wrapper stacking regressions, known blocker classes, and bounded selector-ancestor pairs in the scanned file. It is not a full cascade resolver, cannot see consumer CSS in HTML mode unless it is inline or a compiled-in Casca class, and skips sibling combinators because siblings are not ancestors.

casca check-z-ladder <FILE> enforces the z-index ladder in CSS source. Every z-index declaration must use auto or one of the documented var(--casca-z-*) ladder tokens. Bare numbers, CSS-wide keywords, unknown tokens, fallbacks, and calc() values are rejected.

1. Container Queries (@container)Link to section

Used for: Responsive chart sizing, adaptive layouts

Browser support:

Fallback:

Feature detection:

@supports (container-type: inline-size) {
  /* Container query enhancements */
}

Analytics sets container-type: inline-size on .casca-analytics and uses a container query to stack registers and tighten heading rhythm on narrow containers. A matching viewport media query keeps the layout usable where container queries are unavailable.

2. CSS :has() Pseudo-ClassLink to section

Used for: Dynamic pagination indicator updates, the slot grid's selected / hover / focus visual state (.casca-slot:has(:checked)), the no-JS theme picker preview, and the @layer casca.interactions primitives - series toggle, switch view swap, and filter chips (disclosure uses native <details>, no :has()).

Browser support:

Fallback:

Theme serving combinations:

:has() supportServing layerCookie stateResult
YesAvailablePresent or query overrideRadio preview works immediately, Apply persists, and redirects serve body[data-casca-theme="<id>"].
NoAvailablePresent or query overrideSame-page preview is unavailable, but redirected and linked pages use body[data-casca-theme="<id>"].
YesUnavailableNoneCurrent-page radio preview works, but Apply requires host-provided /_casca/theme support.
NoUnavailableNonePages render the manifest base mood.

Nested filters. :has() cannot be confined to a sub-scope - it matches through a nested .casca-filter boundary, and @scope does not constrain a :has() argument. So nested filters (a .casca-filter inside another) compose only under a contract: each nested filter must use a value / data-filter namespace disjoint from its ancestors (the 1..8 range; sub-range it), and its .casca-toggles + .casca-filter-empty must be direct children of its .casca-filter. With that, the hide rules, the per-filter counter(), and the empty-state all stay scoped to the right filter. See docs/API.md → Filter → Nesting. The non-nested case (the common one) needs none of this.

Feature detection:

@supports selector(:has(*)) {
  /* Dynamic indicator styles */
}

/* Fallback (no :has() support) */
@supports not selector(:has(*)) {
  .casca-legend-indicator-page {
    display: none; /* Hide dynamic indicators */
  }
  .casca-legend-indicator {
    display: block; /* Show static fallback */
  }
}

3. CSS @propertyLink to section

Used for: Animated chart values (smooth transitions)

Browser support:

Fallback:

Example:

@property --casca-value {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 0%;
}

4. CSS Logical PropertiesLink to section

Used for: RTL language support, modern layout primitives

Browser support:

Examples:

5. prefers-color-scheme Media QueryLink to section

Used for: Dark mode support (without setting :root{color-scheme})

Browser support:

Example:

@media (prefers-color-scheme: dark) {
  .casca {
    --casca-gray-0: #1a1a1a;
    --casca-gray-8: #e0e0e0;
  }
}

6. prefers-reduced-motion Media QueryLink to section

Used for: Accessibility (disable animations for users who prefer reduced motion)

Browser support:

Example:

@media (prefers-reduced-motion: reduce) {
  .casca,
  .casca * {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

7. CSS color-mix()Link to section

Used for: Heatmap cell intensity (blends the empty color toward the full color by each cell's --value), table row-selection tint, and prose callout background and border tints.

Browser support:

Fallback:

Feature detection:

@supports not (background: color-mix(in srgb, red, blue)) {
  .casca-heatmap-cell { /* opacity-based fallback */ }
}

Prose ExtensionsLink to section

The v1.5.0 prose extensions are additive and scoped under .casca-prose.

FeatureBaselineFallback behavior
Bold-leading blockquote auto-callouts via :has()Baseline 2023. Chrome 105+, Safari 15.4+, Firefox 121+The blockquote stays a normal blockquote. Explicit .casca-prose-callout still works.
color-mix(in oklab, ...) callout tintingBaseline 2023. Chrome 111+, Safari 16.2+, Firefox 113+Callout background falls back through --casca-page-bg, --casca-surface-1, then --casca-gray-0; borders fall back through --casca-rule, then --casca-gray-3.
Native <details> / <summary>UniversalNone needed. Casca styles the native control only.
Definition lists <dl> / <dt> / <dd>UniversalNone needed.
Heading-anchor ::after glyph and hover revealUniversalNone needed. The SSG emits a real child link for keyboard and screen-reader access.
:focus-visible anchor revealBaseline 2022A paired :focus selector reveals the anchor for legacy keyboard paths.
@media (hover: none) anchor revealEvergreen mobile and touch enginesWithout the media query support, anchors still reveal on keyboard focus.
print-color-adjust: exact on calloutsBaseline 2022Printers may strip the callout tint, but text and borders remain.

The callout color-mix() fallback is intentionally opaque. Mixing against transparent can disappear on dark surfaces, so Casca mixes against --casca-page-bg when supported and uses the same page-background chain as the flat fallback when unsupported.


<casca-layout> Declarative Shadow DOMLink to section

<casca-layout> depends on Declarative Shadow DOM, a Baseline 2024 platform feature: Chrome 111+, Safari 16.4+, and Firefox 123+. In supported engines, the parser attaches the shadow root from the direct <template shadowrootmode="open"> child and paints the header, skip link, hero slot, main slot, and footer chrome from the shadow template.

In older engines, the template is treated as a plain inert template. The slotted light-DOM content remains in the document and renders directly, while the shadow chrome does not render. Casca ships no JavaScript polyfill for this fallback path.

<casca-layout> Paint Timing MeasurementsLink to section

The measurement harness serves tools/release-templates/casca-layout-shell.html from python3 -m http.server over localhost with no throttling, viewport 1280x720, and dist/casca.css at 153,292 bytes. Results are 3-run medians with low / high ranges in parentheses. Cold cache uses a fresh browser context for the first navigation. Warm cache uses page.reload() immediately after cold.

Chromium, Playwright build 1208, headless:

MetricCold cacheWarm cache
First Contentful Paint (FCP)96 ms (88-108)32 ms (32-40)
loadEventEnd74 ms (71-89)19 ms (19-22)
Shadow root attachedyesyes

Firefox, Playwright build 1509, headless:

MetricCold cacheWarm cache
First Contentful Paint (FCP)390 ms (348-440)294 ms (253-304)
loadEventEnd388 ms (347-438)293 ms (252-302)
Shadow root attachedyesyes

WebKit is not measured on this Linux/Fedora 44 environment. Playwright WebKit build 2248 requires libicu.so.74 from the Ubuntu 24.04 ABI, while Fedora 44 ships libicu.so.77; the ABIs are incompatible. Side-loading the Ubuntu shared object is refused per the CLAUDE.md Prime Directive. WebKit paint timing and the functional DSD smoke test are deferred to a macOS Safari host or Ubuntu 24.04 Playwright WebKit host.

These numbers are environment-dependent: machine, cache state, and browser build all affect the result. The methodology above is reproducible with any Playwright-driven headless harness pointed at the shell asset; consumers can re-run on their own hardware for comparison.


8. CSS Trigonometric Functions (cos() / sin())Link to section

Used for: Radial axis labels (.casca-axis[data-axis="radial"]) - places each spoke label on a circle from its --index and --casca-axis-count. Only the radial label geometry uses trig; linear ticks (data-axis="x" / "y") use plain calc() and are universally supported.

Browser support:

Fallback:

Feature detection:

@supports not (inset-block-start: calc(sin(1deg) * 1%)) {
  .casca-axis[data-axis="radial"] { /* flow-layout fallback */ }
}

Feature Detection & Progressive EnhancementLink to section

Casca uses @supports queries for graceful degradation:

Pagination Indicator ExampleLink to section

/* Modern browsers: dynamic indicator */
.casca-legend-paginated:has(input[data-page="1"]:checked)
  .casca-legend-indicator-page[data-page="1"] {
  display: block;
}

/* Fallback: static indicator */
.casca-legend-indicator {
  font-size: var(--casca-font-size-00);
}

/* Hide fallback if :has() is supported */
@supports selector(:has(*)) {
  .casca-legend-indicator {
    display: none;
  }
}

Container Query ExampleLink to section

/* Modern browsers: container-based sizing */
@container casca (max-width: 400px) {
  .casca-bar-value {
    --casca-bar-width: 1rem;
  }
}

/* Fallback: viewport-based sizing */
@media (max-width: 600px) {
  .casca-bar-value {
    --casca-bar-width: 1.5rem;
  }
}

Testing StrategyLink to section

Test Casca in these environments:

  1. Latest Chrome (primary development target)
  2. Latest Firefox (:has() edge cases)
  3. Latest Safari (WebKit quirks, container query differences)
  4. Chrome 105 (minimum supported version)
  5. Firefox 110 (without :has() fallback testing)

Feature Detection in JavaScript (Optional)Link to section

If you need runtime feature detection:

const supportsHas = CSS.supports('selector(:has(*))');
const supportsContainerQueries = CSS.supports('container-type: inline-size');
const supportsAtProperty = CSS.supports('@property', '--test: 0');

console.log({
  has: supportsHas,
  containerQueries: supportsContainerQueries,
  atProperty: supportsAtProperty
});

Accessibility FeaturesLink to section

Casca implements WCAG 2.1 AA guidelines using modern CSS:

High Contrast ModeLink to section

Uses: prefers-contrast media query

@media (prefers-contrast: more) {
  :root {
    --casca-line-stroke-width: 3px;
    --casca-bar-min-height: 4px;
    --casca-grid-color: var(--casca-gray-7);
  }
}

Browser support:

Forced Colors ModeLink to section

Uses: forced-colors media query

@media (forced-colors: active) {
  .casca-bar-value,
  .casca-pie-chart {
    forced-color-adjust: none;
    border: 1px solid CanvasText;
  }
}

Browser support:

Analytics adds scoped forced-colors rules for .casca-analytics-bar, the preliminary dashed bar, integrity notices, registers, and verify links. The preliminary state also has visible still tallying text, so meaning does not depend on hatching or color.

Reduced MotionLink to section

Uses: prefers-reduced-motion media query

@media (prefers-reduced-motion: reduce) {
  .casca * {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

Browser support:

Analytics does not animate counts. Its reduced-motion rule explicitly removes any bar transition if a host theme layers one in.


Polyfills & WorkaroundsLink to section

❌ We Do NOT Recommend PolyfillsLink to section

Casca intentionally avoids polyfills because:

  1. Increases bundle size - Casca is currently 44KB CSS, 0KB JS
  2. Adds runtime overhead - Polyfills execute on every page load
  3. Creates maintenance burden - Polyfill compatibility across browsers
  4. Degrades gracefully - Fallbacks are built-in via @supports

Instead of polyfills:

  1. Target modern browsers - Set browser support policy explicitly
  2. Use fallbacks - Casca has built-in @supports fallbacks
  3. Accept graceful degradation - Older browsers get simpler experiences
  4. Educate users - Recommend browser upgrades for best experience

Example: Browser Upgrade MessageLink to section

<!-- Optional: Detect and warn users on unsupported browsers -->
<script>
  const isSupported = CSS.supports('selector(:has(*))') &&
                       CSS.supports('container-type: inline-size');

  if (!isSupported) {
    console.warn('Your browser does not support all Casca features. ' +
                 'Please upgrade to Chrome 105+, Firefox 121+, or Safari 16+.');
  }
</script>

Known Issues & LimitationsLink to section

Firefox :has() SpecificityLink to section

Issue: Firefox 121+ supports :has() but has different specificity handling than Chrome/Safari.

Workaround: Always use data-page attributes alongside IDs for pagination:

<!-- ✅ Good: Works in all browsers -->
<input type="radio" id="legend-page-1" data-page="1">

<!-- ❌ Bad: May not work consistently -->
<input type="radio" id="legend-page-1">

Safari Container Query RoundingLink to section

Issue: Safari 16.0-16.3 has container query size rounding issues.

Impact: Chart widths may be off by 1-2 pixels.

Workaround: Use Safari 16.4+ or accept minor visual differences.

Chrome @property with Custom UnitsLink to section

Issue: @property doesn't support all custom units (e.g., <angle> with turn).

Workaround: Stick to deg for angles, % for percentages.


Performance ConsiderationsLink to section

CSS PerformanceLink to section

Casca is CSS-only, so performance is excellent:

Rendering PerformanceLink to section

Modern CSS features are highly optimized:

Best PracticesLink to section

  1. Avoid excessive nesting:

    /* ❌ Slow */
    .casca:has(.deeply > .nested > .selector:checked) .target { }
    
    /* ✅ Fast */
    .casca:has(input[data-page="1"]:checked) .target { }
    
  2. Use will-change sparingly:

    .casca-bar-value {
      /* Only if animating frequently */
      will-change: height;
    }
    
  3. Minimize @property usage:

    • Only animate values that need smooth transitions
    • Firefox doesn't support @property and won't animate smoothly

Future CompatibilityLink to section

Casca will continue targeting evergreen modern browsers. We will:

Upcoming CSS Features to WatchLink to section


TypographyLink to section

Casca's design system specifies four typefaces (DESIGN.md §Typography). The core library ships only fallback stacks in --casca-font-* tokens; no @font-face declarations, no font assets. Consumers wire their preferred faces via their own @font-face block.

The portal self-hosts all four faces under open licenses. The recipe lives at site/assets/css/casca-fonts.css and is reusable by external consumers (copy the file, copy the assets, link before casca.css).

Font tokensLink to section

TokenStackPurpose
--casca-font-display'Departure Mono', 'JetBrains Mono', 'IBM Plex Mono', monospaceWordmark, headers, picker
--casca-font-sans'DM Sans', system-ui, ...UI / labels
--casca-font-serif'Newsreader', 'Iowan Old Style', Georgia, serifBody prose
--casca-font-mono'JetBrains Mono', ui-monospace, ...Code, data tables

Portal self-host bytesLink to section

FaceFileSize
Departure Mono RegularDepartureMono-Regular.woff221.9 KB
Newsreader RegularNewsreader-Regular.woff222.0 KB
Newsreader ItalicNewsreader-Italic.woff223.8 KB
DM Sans Regular (400)DMSans-Regular.woff213.9 KB
DM Sans Medium (500)DMSans-Medium.woff214.0 KB
DM Sans SemiBold (600)DMSans-SemiBold.woff213.8 KB
JetBrains Mono RegularJetBrainsMono-Regular.woff220.7 KB
Total130.0 KB

All seven faces are subset to the Latin character set (Basic Latin + Latin-1 Supplement + smart quotes), the Block Elements range (U+2591– U+2593, used by .casca-divider-pixel), and standard punctuation.

FOUT policyLink to section

Every @font-face declaration ships with font-display: swap. The fallback stack paints first; the named face swaps in once loaded. DESIGN.md prefers FOUT (paint immediately, swap when ready) over FOIT (blank until loaded) because the fallback stacks are deliberately typographically close to the named faces, so the swap is gentle.

LicensesLink to section

FaceLicense
Departure MonoSIL Open Font License 1.1
NewsreaderSIL Open Font License 1.1
DM SansSIL Open Font License 1.1
JetBrains MonoSIL Open Font License 1.1

All four allow embedding, modification, and redistribution. The portal ships them under their original licenses; consumers self-hosting follow the same terms.


Browser Testing ResourcesLink to section


SummaryLink to section

Minimum Requirements:

Graceful Degradation:

Performance:

For questions or compatibility issues, file a bug report with your browser version and the specific feature failing.