Browser Compatibility
Casca targets evergreen modern browsers and uses cutting-edge CSS features for maximum simplicity and performance.
Browser Support Matrix
✅ Fully Supported (All Features)
| Browser | Minimum Version | Release Date | Notes |
|---|---|---|---|
| Chrome | 105+ | Aug 2022 | Container queries, :has() |
| Edge | 105+ | Sep 2022 | Chromium-based |
| Safari | 16.0+ | Sep 2022 | :has() support |
| Firefox | 121+ | Dec 2023 | :has() support (latest requirement) |
| Opera | 91+ | Oct 2022 | Chromium-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)
Older browsers may render charts but with degraded features:
| Browser | Version | Limitation | Fallback Behavior |
|---|---|---|---|
| Safari | 15.4-15.9 | No :has() for legend pagination | Static page indicator ("Pages: 1-3") |
| Firefox | 110-120 | No :has() for legend pagination | Static page indicator |
| Chrome | 88-104 | No container queries | Fixed chart sizing |
| Chrome 110 and older / Safari 16.3 and older / Firefox 122 and older | No Declarative Shadow DOM for <casca-layout> | The <template shadowrootmode> stays an inert template; slotted light-DOM content renders directly without the shadow chrome | |
| Chrome | 105-110 / Safari 16.0-16.1 | No color-mix() for heatmap intensity or the table row-selection tint | Heatmap: opacity (approximate); selected rows: no tint, but the checked checkbox still conveys selection |
| Safari | 15.0-15.3 | No accent-color for the range slider | Slider renders with the default system accent (still functional, just not brand-colored) |
❌ Not Supported
- Internet Explorer (all versions)
- Chrome < 88
- Firefox < 110
- Safari < 15.4
- Edge Legacy (pre-Chromium)
Optional live layer (dist/casca-live.js)
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 Features
Casca relies on these modern CSS features:
The build pipeline uses lightningcss 1.0.0-alpha.71, which enters the
build only as the lightningcss-rs crate dependency inside bin/casca.
casca build uses 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 build 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.
Modal stacking and lint enforcement
.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)
Used for: Responsive chart sizing, adaptive layouts
Browser support:
- Chrome 105+, Safari 16+, Firefox 110+
Fallback:
- Charts use fixed
--casca-heightand--casca-widthvalues - Responsive sizing via viewport queries still works
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-Class
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:
- Chrome 105+, Safari 15.4+, Firefox 121+
Fallback:
- Paginated legend shows static "Pages: 1-N" instead of dynamic "Page 2 of N"
- Pagination functionality still works (radio buttons control page visibility)
- Slot grid: without
:has()the radios still work and submit normally (selection and keyboard nav are native), but the highlight of the chosen slot is not shown. Functionality is unaffected; only the visual cue degrades. - Theme picker: without
:has(), same-page preview is unavailable. A persisted mood still works throughbody[data-casca-theme="<id>"]when the serving layer emits that attribute. Without that serving layer, pages render the base mood. - Interactions: without
:has(), everything stays visible - series toggle shows all series, filter shows all items, switch shows the first view - and the native controls (checkboxes, radios) still operate. Disclosure (<details>) works everywhere regardless.
Theme serving combinations:
:has() support | Serving layer | Cookie state | Result |
|---|---|---|---|
| Yes | Available | Present or query override | Radio preview works immediately, Apply persists, and redirects serve body[data-casca-theme="<id>"]. |
| No | Available | Present or query override | Same-page preview is unavailable, but redirected and linked pages use body[data-casca-theme="<id>"]. |
| Yes | Unavailable | None | Current-page radio preview works, but Apply requires host-provided /_casca/theme support. |
| No | Unavailable | None | Pages 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 @property
Used for: Animated chart values (smooth transitions)
Browser support:
- Chrome 85+, Edge 85+, Opera 71+
- Safari 16.4+
- Firefox: Not supported (as of Dec 2023)
Fallback:
- Transitions still occur but may be less smooth in Firefox
- Charts remain fully functional
Example:
@property --casca-value {
syntax: "<percentage>";
inherits: false;
initial-value: 0%;
}
4. CSS Logical Properties
Used for: RTL language support, modern layout primitives
Browser support:
- Universal in modern browsers (Chrome 89+, Safari 14.1+, Firefox 68+)
Examples:
margin-inline,padding-block,inset-inline-start
5. prefers-color-scheme Media Query
Used for: Dark mode support (without setting :root{color-scheme})
Browser support:
- Universal in modern browsers (Chrome 76+, Safari 12.1+, Firefox 67+)
Example:
@media (prefers-color-scheme: dark) {
.casca {
--casca-gray-0: #1a1a1a;
--casca-gray-8: #e0e0e0;
}
}
6. prefers-reduced-motion Media Query
Used for: Accessibility (disable animations for users who prefer reduced motion)
Browser support:
- Universal in modern browsers (Chrome 74+, Safari 10.1+, Firefox 63+)
Example:
@media (prefers-reduced-motion: reduce) {
.casca,
.casca * {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
}
}
7. CSS color-mix()
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:
- Chrome 111+, Safari 16.2+, Firefox 113+
Fallback:
- Heatmap cells approximate intensity with
opacityover the full color - Prose callouts use an opaque page-background fallback, a normal rule border, and the full-color leading accent stripe.
- Other charts are unaffected (they do not use
color-mix())
Feature detection:
@supports not (background: color-mix(in srgb, red, blue)) {
.casca-heatmap-cell { /* opacity-based fallback */ }
}
Prose Extensions
The v1.5.0 prose extensions are additive and scoped under .casca-prose.
| Feature | Baseline | Fallback 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 tinting | Baseline 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> | Universal | None needed. Casca styles the native control only. |
Definition lists <dl> / <dt> / <dd> | Universal | None needed. |
Heading-anchor ::after glyph and hover reveal | Universal | None needed. The SSG emits a real child link for keyboard and screen-reader access. |
:focus-visible anchor reveal | Baseline 2022 | A paired :focus selector reveals the anchor for legacy keyboard paths. |
@media (hover: none) anchor reveal | Evergreen mobile and touch engines | Without the media query support, anchors still reveal on keyboard focus. |
print-color-adjust: exact on callouts | Baseline 2022 | Printers 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 DOM
<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.
Whole-page shells should still use <body class="casca">. The core bundle's
body rule is not a Declarative Shadow DOM feature; it applies in older engines
too, removing the browser default body margin and painting the page canvas with
Casca background and ink tokens even when the shadow chrome is unavailable.
<casca-layout> Paint Timing Measurements
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:
| Metric | Cold cache | Warm cache |
|---|---|---|
| First Contentful Paint (FCP) | 96 ms (88-108) | 32 ms (32-40) |
loadEventEnd | 74 ms (71-89) | 19 ms (19-22) |
| Shadow root attached | yes | yes |
Firefox, Playwright build 1509, headless:
| Metric | Cold cache | Warm cache |
|---|---|---|
| First Contentful Paint (FCP) | 390 ms (348-440) | 294 ms (253-304) |
loadEventEnd | 388 ms (347-438) | 293 ms (252-302) |
| Shadow root attached | yes | yes |
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())
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:
- Chrome 111+, Safari 15.4+, Firefox 108+ (Baseline 2023)
Fallback:
- When trig is unavailable, the radial band drops to normal flow as a centered, wrapped row of labels - degraded placement, fully readable text
- The
.casca-datatable carries the values regardless
Feature detection:
@supports not (inset-block-start: calc(sin(1deg) * 1%)) {
.casca-axis[data-axis="radial"] { /* flow-layout fallback */ }
}
Feature Detection & Progressive Enhancement
Casca uses @supports queries for graceful degradation:
Pagination Indicator Example
/* 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 Example
/* 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 Strategy
Recommended Test Matrix
Test Casca in these environments:
- Latest Chrome (primary development target)
- Latest Firefox (
:has()edge cases) - Latest Safari (WebKit quirks, container query differences)
- Chrome 105 (minimum supported version)
- Firefox 110 (without
:has()fallback testing)
Feature Detection in JavaScript (Optional)
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 Features
Casca implements WCAG 2.1 AA guidelines using modern CSS:
High Contrast Mode
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:
- Chrome 96+, Edge 96+, Safari 14.1+
- Firefox: Not yet supported
Forced Colors Mode
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:
- Chrome 89+, Edge 79+
- Safari/Firefox: Limited 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 Motion
Uses: prefers-reduced-motion media query
@media (prefers-reduced-motion: reduce) {
.casca * {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
}
}
Browser support:
- Universal in modern browsers
Analytics does not animate counts. Its reduced-motion rule explicitly removes any bar transition if a host theme layers one in.
Polyfills & Workarounds
❌ We Do NOT Recommend Polyfills
Casca intentionally avoids polyfills because:
- Increases bundle size - Casca is currently 44KB CSS, 0KB JS
- Adds runtime overhead - Polyfills execute on every page load
- Creates maintenance burden - Polyfill compatibility across browsers
- Degrades gracefully - Fallbacks are built-in via
@supports
✅ Recommended Approach
Instead of polyfills:
- Target modern browsers - Set browser support policy explicitly
- Use fallbacks - Casca has built-in
@supportsfallbacks - Accept graceful degradation - Older browsers get simpler experiences
- Educate users - Recommend browser upgrades for best experience
Example: Browser Upgrade Message
<!-- 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 & Limitations
Firefox :has() Specificity
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 Rounding
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 Units
Issue: @property doesn't support all custom units (e.g., <angle> with turn).
Workaround: Stick to deg for angles, % for percentages.
Performance Considerations
CSS Performance
Casca is CSS-only, so performance is excellent:
- No JavaScript parsing/execution
- No runtime layout calculations
- GPU-accelerated transitions (via
transformandopacity) - Small bundle size (44KB CSS vs typical 200KB+ JS charting libraries)
Rendering Performance
Modern CSS features are highly optimized:
- Container queries: Near-zero overhead (native browser feature)
- CSS custom properties: Fast variable resolution
:has()selector: Optimized in modern browsers (avoid deep nesting)
Best Practices
-
Avoid excessive nesting:
/* ❌ Slow */ .casca:has(.deeply > .nested > .selector:checked) .target { } /* ✅ Fast */ .casca:has(input[data-page="1"]:checked) .target { } -
Use
will-changesparingly:.casca-bar-value { /* Only if animating frequently */ will-change: height; } -
Minimize
@propertyusage:- Only animate values that need smooth transitions
- Firefox doesn't support
@propertyand won't animate smoothly
Future Compatibility
Casca will continue targeting evergreen modern browsers. We will:
- ✅ Adopt new CSS features as they reach stable support (85%+ browser market share)
- ✅ Provide
@supportsfallbacks for new features - ✅ Drop support for browsers that fall below 1% market share
- ❌ NOT support browsers that are no longer maintained (IE, old Edge, etc.)
Upcoming CSS Features to Watch
- CSS Anchor Positioning - For tooltips and popovers
- CSS Nesting - For cleaner source code (already in preprocessor)
- CSS Color Level 4 -
oklch(),color-mix(), relative colors - View Transitions API - For page navigation animations
Typography
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 tokens
| Token | Stack | Purpose |
|---|---|---|
--casca-font-display | 'Departure Mono', 'JetBrains Mono', 'IBM Plex Mono', monospace | Wordmark, headers, picker |
--casca-font-sans | 'DM Sans', system-ui, ... | UI / labels |
--casca-font-serif | 'Newsreader', 'Iowan Old Style', Georgia, serif | Body prose |
--casca-font-mono | 'JetBrains Mono', ui-monospace, ... | Code, data tables |
Portal self-host bytes
| Face | File | Size |
|---|---|---|
| Departure Mono Regular | DepartureMono-Regular.woff2 | 21.9 KB |
| Newsreader Regular | Newsreader-Regular.woff2 | 22.0 KB |
| Newsreader Italic | Newsreader-Italic.woff2 | 23.8 KB |
| DM Sans Regular (400) | DMSans-Regular.woff2 | 13.9 KB |
| DM Sans Medium (500) | DMSans-Medium.woff2 | 14.0 KB |
| DM Sans SemiBold (600) | DMSans-SemiBold.woff2 | 13.8 KB |
| JetBrains Mono Regular | JetBrainsMono-Regular.woff2 | 20.7 KB |
| Total | 130.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 policy
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.
Licenses
| Face | License |
|---|---|
| Departure Mono | SIL Open Font License 1.1 |
| Newsreader | SIL Open Font License 1.1 |
| DM Sans | SIL Open Font License 1.1 |
| JetBrains Mono | SIL 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 Resources
- Can I Use - Feature compatibility tables
- MDN Web Docs - Canonical browser compatibility info
- BrowserStack - Cross-browser testing (paid)
- LambdaTest - Cross-browser testing (free tier)
Summary
Minimum Requirements:
- Chrome 105+, Edge 105+, Safari 16+, Firefox 121+
- Modern CSS support (
:has(), container queries) - Evergreen browser update policy
Graceful Degradation:
- Older browsers get simpler features (static indicators, fixed sizing)
- All core functionality remains intact
- No polyfills required
Performance:
- 44KB CSS, 0KB JS
- GPU-accelerated animations
- Near-zero runtime overhead
For questions or compatibility issues, file a bug report with your browser version and the specific feature failing.
Linked references
- Cascalink
- linked as Compatibility
- Casca Adoption Guidelink
- linked as Compatibility