diff --git a/apps/docs/src/components/docs/Demo.astro b/apps/docs/src/components/docs/Demo.astro
index cf70046..ce1618b 100644
--- a/apps/docs/src/components/docs/Demo.astro
+++ b/apps/docs/src/components/docs/Demo.astro
@@ -10,19 +10,51 @@ interface Props {
showCode?: boolean;
/** Language for syntax highlighting (default: html) */
lang?: 'html' | 'css' | 'js';
+ /**
+ * Force a specific theme for this demo ("dark" | "light" | "split")
+ * - "dark": Demo preview uses dark theme context
+ * - "light": Demo preview uses light theme context
+ * - "split": Shows both light and dark side-by-side
+ * - undefined: Follows system/page theme
+ */
+ theme?: 'dark' | 'light' | 'split';
}
-const { code, title, showCode = true, lang = 'html' } = Astro.props;
+const { code, title, showCode = true, lang = 'html', theme } = Astro.props;
// Clean up the code for display (trim whitespace)
const cleanCode = code.trim();
+
+// Determine container classes
+const previewClass = theme === 'split' ? 'demo-preview demo-preview--split' : 'demo-preview';
---
{title &&
{title}
}
-
-
-
+ {theme === 'split' ? (
+
+ ) : theme ? (
+
+
+
+ ) : (
+
+
+
+ )}
{showCode && (
@@ -52,6 +84,71 @@ const cleanCode = code.trim();
background: var(--s-surface-default);
}
+ /* Theme island support - forced dark/light contexts */
+ .demo-preview[s-theme='dark'] {
+ background: oklch(12% 0.025 220);
+ color: oklch(95% 0.01 220);
+ }
+
+ .demo-preview[s-theme='light'] {
+ background: oklch(98% 0.005 220);
+ color: oklch(8% 0.02 220);
+ }
+
+ /* Split view - side-by-side light and dark */
+ .demo-preview--split {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ padding: 0;
+ gap: 1px;
+ background: var(--s-border-default);
+ }
+
+ .demo-preview-pane {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .demo-preview-pane[s-theme='light'] {
+ background: oklch(98% 0.005 220);
+ color: oklch(8% 0.02 220);
+ }
+
+ .demo-preview-pane[s-theme='dark'] {
+ background: oklch(12% 0.025 220);
+ color: oklch(95% 0.01 220);
+ }
+
+ .demo-pane-header {
+ padding: 0.375rem 0.75rem;
+ font-size: 10px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ }
+
+ /* Ensure accessible contrast for labels */
+ .demo-preview-pane[s-theme='light'] .demo-pane-header {
+ color: oklch(45% 0.02 220);
+ border-bottom: 1px solid oklch(85% 0.01 220);
+ }
+
+ .demo-preview-pane[s-theme='dark'] .demo-pane-header {
+ color: oklch(75% 0.02 220);
+ border-bottom: 1px solid oklch(25% 0.02 220);
+ }
+
+ .demo-pane-content {
+ padding: var(--s-space-6);
+ }
+
+ /* Responsive: stack on mobile */
+ @media (max-width: 640px) {
+ .demo-preview--split {
+ grid-template-columns: 1fr;
+ }
+ }
+
.demo-code {
border-top: 1px solid var(--s-border-default);
background: var(--s-surface-sunken);
diff --git a/apps/docs/src/content/docs/utilities/functions.mdx b/apps/docs/src/content/docs/utilities/functions.mdx
index 948256f..70f351f 100644
--- a/apps/docs/src/content/docs/utilities/functions.mdx
+++ b/apps/docs/src/content/docs/utilities/functions.mdx
@@ -251,12 +251,76 @@ Respects reduced motion preferences:
### s-dark-aware
-Adapts background to color scheme:
+A sophisticated dark-awareness system that goes beyond simple color swaps. It provides:
+
+1. **Container-Relative Theming** - Uses CSS style queries to detect parent theme context, not just system preference
+2. **OKLCH Perceptual Contrast** - Auto-computes readable text color based on background lightness
+3. **Visual Adaptations** - Subtle border glow prevents bleeding, font weight adjusts for halation
+
+
+ Self-Healing Card
+ Text contrast, border glow, and font weight all adapt automatically.
+ `}
+/>
+
+#### Theme Containers
+
+Create "dark islands" on light pages (or vice versa) with `s-theme`:
+
+```html
+
+
+
+
+
Falls back to OS preference
+```
+
+
+
+
+
Light Island
+
Forced light context
+
+
+
+
+
Dark Island
+
Forced dark context
+
+
+ `}
+/>
+
+#### Variants
```html
-Background changes in dark mode
+
+Full adaptation
+
+
+For inline elements
+
+
+Maximum visual separation
```
+#### Technical Details
+
+| Feature | Light Mode | Dark Mode |
+|---------|------------|-----------|
+| Text Color | OKLCH L=0.15 | OKLCH L=0.95 |
+| Border Glow | None | 0.5px white @ 8% |
+| Font Weight | Base | Base - 50 |
+
+The font weight reduction counters **halation** - the optical illusion where light text on dark backgrounds appears "fatter" due to light bleed in human vision.
+
## Creating Custom Functions
Define your own reusable CSS functions:
diff --git a/packages/core/src/utils/functions.css b/packages/core/src/utils/functions.css
index 78a6a5d..cfe1b9d 100644
--- a/packages/core/src/utils/functions.css
+++ b/packages/core/src/utils/functions.css
@@ -208,18 +208,152 @@
}
/**
- * Dark mode aware styling using if()
+ * Dark Mode Aware Styling
+ *
+ * A sophisticated dark-awareness system that goes beyond simple color swaps:
+ *
+ * 1. CONTAINER-RELATIVE THEMING
+ * Uses CSS style queries to detect parent theme context, not just system preference.
+ * A dark sidebar on a light-mode site will correctly theme its children.
+ *
+ * 2. PERCEPTUAL OKLCH CONTRAST
+ * Uses OKLCH lightness math to ensure text readability on any background.
+ * No manual light/dark text color definitions needed.
+ *
+ * 3. VISUAL ADAPTATION BEYOND COLOR
+ * - Border glow: Subtle inner highlight prevents dark elements from "bleeding"
+ * - Font weight: Reduces weight ~50 units to counter halation (light-on-dark bloat)
+ *
+ * Usage:
+ *
+ *
+ *
+ * Content here
+ *
*/
+
+/* =============================================================================
+ THEME CONTAINER SETUP
+ Containers that establish a theme context for descendants
+ ============================================================================= */
+
+/**
+ * Theme containers - establish dark/light context for style queries
+ * These create a "theme boundary" that s-dark-aware elements can detect
+ */
+[s-theme] {
+ container-name: theme;
+}
+
+[s-theme="dark"] {
+ --_theme-is-dark: 1;
+ color-scheme: dark;
+ background-color: var(--s-surface-sunken, oklch(12% 0.02 220));
+ color: var(--s-text-primary, oklch(95% 0.01 220));
+}
+
+[s-theme="light"] {
+ --_theme-is-dark: 0;
+ color-scheme: light;
+ background-color: var(--s-surface-raised, oklch(98% 0.005 220));
+ color: var(--s-text-primary, oklch(15% 0.02 220));
+}
+
+/* =============================================================================
+ DARK-AWARE COMPONENT
+ Self-healing elements that adapt to their theme context
+ ============================================================================= */
+
[s-dark-aware] {
- /* Fallback for non-supporting browsers */
- --_bg: var(--s-surface-raised);
- background-color: var(--_bg);
+ /**
+ * LAYER 1: System preference baseline (universal fallback)
+ * Works in all browsers, detects OS-level dark mode
+ */
+ --_da-is-dark: 0;
+ --_da-bg: var(--s-surface-raised);
+
+ @media (prefers-color-scheme: dark) {
+ --_da-is-dark: 1;
+ --_da-bg: var(--s-surface-sunken);
+ }
- --_bg: if(
- media(prefers-color-scheme: dark): var(--s-surface-sunken);
- else: var(--s-surface-raised);
- );
- background-color: var(--_bg);
+ /**
+ * LAYER 2: Container-relative theming via style queries
+ * If inside an [s-theme] container, inherit its theme regardless of system preference
+ * This enables "dark islands" on light sites and vice versa
+ */
+ @supports (container-type: inline-size) {
+ @container theme style(--_theme-is-dark: 1) {
+ --_da-is-dark: 1;
+ --_da-bg: var(--s-surface-sunken);
+ }
+
+ @container theme style(--_theme-is-dark: 0) {
+ --_da-is-dark: 0;
+ --_da-bg: var(--s-surface-raised);
+ }
+ }
+
+ /* Apply computed background */
+ background-color: var(--_da-bg);
+
+ /**
+ * LAYER 3: OKLCH Perceptual Contrast Text
+ * Automatically compute readable text color based on background lightness
+ * Uses L=0.55 threshold for WCAG 2.1 AA 4.5:1 contrast
+ */
+ color: var(--s-text-primary); /* Fallback */
+
+ @supports (color: oklch(from red l c h)) {
+ color: oklch(
+ from var(--_da-bg)
+ /* If background L < 0.55: use light text (L=0.95), else dark text (L=0.15) */
+ clamp(0.15, calc((0.55 - l) * 1000 + 0.15), 0.95)
+ 0 0
+ );
+ }
+
+ /**
+ * LAYER 4: Border Glow (Dark Mode Anti-Bleed)
+ * In dark mode, elements can "bleed" into dark backgrounds.
+ * A subtle inner highlight (0.5px at 8% white) creates visual separation.
+ * Only applies when --_da-is-dark is 1.
+ */
+ --_da-glow-opacity: calc(var(--_da-is-dark) * 0.08);
+ box-shadow: inset 0 0 0 0.5px oklch(1 0 0 / var(--_da-glow-opacity));
+
+ /**
+ * LAYER 5: Font Weight Compensation (Halation Correction)
+ * Light text on dark backgrounds appears "fatter" due to light bleed (halation).
+ * We reduce font-weight by 50 units in dark mode to maintain visual consistency.
+ * Uses @property for type-safe font-weight interpolation.
+ */
+ --_da-weight-base: var(--s-font-weight-normal, 400);
+ --_da-weight-adjustment: calc(var(--_da-is-dark) * 50);
+ font-weight: calc(var(--_da-weight-base) - var(--_da-weight-adjustment));
+}
+
+/**
+ * Variant: s-dark-aware="subtle"
+ * Less aggressive adaptations for inline/text elements
+ */
+[s-dark-aware="subtle"] {
+ /* No background change, just text and weight */
+ background-color: transparent;
+ box-shadow: none;
+}
+
+/**
+ * Variant: s-dark-aware="strong"
+ * More pronounced dark mode effects for hero sections
+ */
+[s-dark-aware="strong"] {
+ --_da-glow-opacity: calc(var(--_da-is-dark) * 0.12);
+ box-shadow:
+ inset 0 0 0 0.5px oklch(1 0 0 / var(--_da-glow-opacity)),
+ inset 0 1px 0 0 oklch(1 0 0 / calc(var(--_da-is-dark) * 0.05));
}
/* =============================================================================