backdrop-filter on magic-side-nav establishes a new containing block for its fixed-positioned children (.nav-sidenav drawer and .nav-backdrop-mobile overlay). On mobile the host shrinks to width:0, collapsing both children off-screen so the drawer never opens — same trap that was fixed for liquid-glass. Move background + backdrop-filter to the inner .nav-sidenav. Add a guard comment on the component :host and a "Authoring Themes" section in docs/styling-guide.md so future themes don't reintroduce this.
10 KiB
Styling Guide
Rules
- All visual styling must use CSS variables from
src/styles/_css-variables.scss— never hardcode colors, spacing, shadows, transitions, or z-index. - Positioning/layout is fine as plain CSS — flexbox, grid, display, position, dimensions.
- Check
src/app/ui/first before creating new styled elements — 40+ reusable components exist. - Component SCSS should be minimal — shared styles belong in
src/styles/components/or as a mixin. - Material overlay components (menus, dialogs, tooltips) render outside component scope — style them in
src/styles/components/and add a comment in the component pointing there.
Anti-Patterns
| Avoid | Do Instead |
|---|---|
Hardcoded colors (#fff, red) |
CSS variables (--text-color, --card-bg, --color-danger) |
Hardcoded spacing (16px, 1rem) |
Spacing variables (--s2, --s, --s-half) |
| Hardcoded shadows | Elevation variables (--whiteframe-shadow-*dp, --md-sys-level*) |
| Hardcoded transitions/durations | Transition variables (--transition-standard, --transition-duration-*) |
| Custom z-index values | Z-index variables (--z-main-header, --z-backdrop, etc.) |
| New styled elements without checking | Check src/app/ui/ for existing reusable components first |
Key Files
| File | Purpose |
|---|---|
src/styles/_css-variables.scss |
All CSS custom properties (design tokens) |
src/styles/themes.scss |
Material theme setup + utility classes |
src/styles/page.scss |
Global page/body styles |
src/styles/util.scss |
Utility classes |
src/styles/components/ |
Global component styles (Material overrides, shared patterns) |
src/styles/mixins/ |
Reusable SCSS mixins |
src/app/ui/ |
40+ reusable Angular UI components |
Spacing Variables (8px Grid)
| Variable | Value | Variable | Value |
|---|---|---|---|
--s-quarter |
2px | --s4 |
32px |
--s-half |
4px | --s5 |
40px |
--s |
8px | --s6 |
48px |
--s2 |
16px | --s7 |
56px |
--s3 |
24px | --s8 |
64px |
// ✅ Good
padding: var(--s2) var(--s3);
gap: var(--s-half);
// ❌ Bad
padding: 16px 24px;
gap: 4px;
Color Variables
| Use case | Variables |
|---|---|
| Text | --text-color, --text-color-muted, --text-color-most-intense |
| Backgrounds | --bg, --card-bg, --task-c-bg, --sub-task-c-bg |
| Semantic | --color-success (#4caf50), --color-warning (#ff9800), --color-danger (#f44336) |
| Material palette | --palette-primary-500, --palette-accent-500, --palette-warn-500 (100–900) |
| Overlays | --c-dark-10 through --c-dark-90, --c-light-05 through --c-light-90 |
| Alpha coefficients | --border-alpha (0.12), --overlay-alpha (0.1), --muted-alpha (0.6), --separator-alpha (0.3) |
Theme-Specific Values
Light theme sets: --bg: #f8f8f7, --card-bg: #ffffff, --text-color: rgb(44, 44, 44)
Dark theme sets: --bg: #131314, --card-bg: var(--dark3), --text-color: rgb(230, 230, 230)
Dark elevation colors: --dark0 (rgb(0,0,0)) through --dark24 (rgb(56,56,56))
Theme-Specific Overrides in Components
@include darkTheme() {
/* dark-only styles */
}
@include lightTheme() {
/* light-only styles */
}
Mixins are in src/styles/mixins/_theming.scss.
Shadows & Elevation
--whiteframe-shadow-1dpthrough--whiteframe-shadow-24dp— classic Material shadows--md-sys-level1through--md-sys-level5— Material Design 3 style
Transitions & Animations
| Type | Variables |
|---|---|
| Shorthands | --transition-fast, --transition-standard, --transition-enter, --transition-leave |
| Durations | --transition-duration-xs (90ms), -s (150ms), -m (225ms), -l (375ms) |
| Additional | --transition-duration-enter (225ms), --transition-duration-leave (195ms), --page-transition-duration (225ms) |
| Timing | --ani-standard-timing, --ani-enter-timing, --ani-leave-timing, --ani-sharp-timing |
When migrating hardcoded durations, pick the nearest bucket — up to ~15% drift is acceptable for UI transitions.
Focus Ring
Keyboard-accessibility tokens for custom interactive elements. Material components keep their own focus treatment; use these for non-Material buttons, cards, and custom controls.
| Variable | Value |
|---|---|
--focus-ring-width |
2px |
--focus-ring-offset |
2px |
--focus-ring-color |
var(--palette-primary-500) |
Quickest adoption — add the .focus-ring utility class from util.scss, which applies an outline on :focus-visible only (so it doesn't fire on mouse clicks).
// opt-in per-element
.my-button:focus-visible {
outline: var(--focus-ring-width) solid var(--focus-ring-color);
outline-offset: var(--focus-ring-offset);
}
Z-Index Layers
| Variable | Value | Purpose |
|---|---|---|
--z-check-done |
11 | Task done checkbox |
--z-main-header |
12 | Main header |
--z-task-title-focus |
32 | Focused task title |
--z-mobile-bottom-nav |
50 | Mobile bottom navigation |
--z-side-nav |
60 | Side navigation |
--z-backdrop |
222 | Backdrop overlay |
--z-add-task-bar |
999 | Add task bar |
--z-search-bar |
999 | Search bar |
--z-tour |
1001 | Tour overlay |
Layout Variables
| Variable | Value | Notes |
|---|---|---|
--component-max-width |
800px | 900–1000px on iPad |
--side-nav-width |
200px | |
--side-nav-width-l |
400px | |
--bar-height-large |
56px | |
--bar-height |
48px | |
--bar-height-small |
40px |
Responsive Breakpoints
Available as CSS vars (--layout-xxxs through --layout-xl) and as SCSS mixins in src/styles/mixins/_media-queries.scss:
| Breakpoint | Value |
|---|---|
xxxs |
398px |
xxs |
450px |
xs |
600px |
s |
800px |
m |
1000px |
l |
1200px |
xl |
2000px |
Utility Classes
Defined in src/styles/util.scss and src/styles/themes.scss:
- Layout:
.center-wrapper,.mw(max-width container) - Responsive:
.hide-xs,.hide-xxs,.hide-gt-sm - Input:
.show-only-on-touch-primary,.show-only-on-mouse-primary - Theme:
.show-dark-only,.show-light-only - Color:
.bg-primary,.bgc-accent,.color-primary,.bg-success,.bg-warning,.bg-danger - Effects:
.milk-glass(backdrop blur)
Authoring Themes
Theme files live in src/assets/themes/*.css and are loaded at runtime.
Side nav: never apply CB-creating properties to the magic-side-nav host
The mobile drawer (.nav-sidenav) and its overlay (.nav-backdrop-mobile) are position: fixed children of the magic-side-nav host. On mobile the host shrinks to width: 0 (hostWidthSignal in magic-side-nav.component.ts).
Any property that establishes a new containing block for fixed-position descendants — backdrop-filter, filter, transform, perspective, contain: paint|layout|strict, or a matching will-change — re-anchors the drawer and backdrop to the 0-wide host. The drawer never appears when the user taps the menu.
Apply glass/blur/tint to the inner .nav-sidenav instead:
/* ❌ Bad — collapses the mobile drawer */
body.isDarkTheme magic-side-nav {
background: var(--my-pane);
backdrop-filter: blur(32px);
}
/* ✅ Good — host stays visually inert */
body.isDarkTheme magic-side-nav .nav-sidenav {
background: var(--my-pane);
backdrop-filter: blur(32px);
}
Properties that do NOT create a containing block — background, border, box-shadow, margin — are safe on the host. See velvet.css and liquid-glass.css for full reference patterns.
Global Component Styles
Located in src/styles/components/, these are needed for elements that render outside component scope:
_overwrite-material.scss— Material component customizations_customizer-menu.scss,backdrop.scss,bottom-panel.scssmarkdown.scss,mentions.scss,table.scssfab-wrapper.scss,wrap-buttons.scss,multi-btn-wrapper.scssplanner-shared.scss,formly-rows.scss,scrollbars.scss