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
| 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)Link to section
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 SupportedLink to section
- Internet Explorer (all versions)
- Chrome < 88
- Firefox < 110
- Safari < 15.4
- Edge Legacy (pre-Chromium)
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.
Modal stacking and lint enforcementLink to section
.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:
- 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-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:
- 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 @propertyLink to section
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 PropertiesLink to section
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 QueryLink to section
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 QueryLink to section
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()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:
- 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 ExtensionsLink to section
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 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:
| 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())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:
- 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 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
Recommended Test MatrixLink to section
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)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:
- Chrome 96+, Edge 96+, Safari 14.1+
- Firefox: Not yet supported
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:
- 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 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:
- 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 & WorkaroundsLink to section
❌ We Do NOT Recommend PolyfillsLink to section
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 ApproachLink to section
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 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:
- 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 PerformanceLink to section
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 PracticesLink to section
-
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 CompatibilityLink to section
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 WatchLink to section
- 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
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
| 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 bytesLink to section
| 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 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
| 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 ResourcesLink to section
- Can I Use - Feature compatibility tables
- MDN Web Docs - Canonical browser compatibility info
- BrowserStack - Cross-browser testing (paid)
- LambdaTest - Cross-browser testing (free tier)
SummaryLink to section
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.