super-productivity/docs/styling-guide.md
Johannes Millan d659ea9d4d fix(theme): apply velvet sidenav blur on inner element, not host
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.
2026-05-14 14:41:08 +02:00

10 KiB
Raw Blame History

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 (100900)
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-1dp through --whiteframe-shadow-24dp — classic Material shadows
  • --md-sys-level1 through --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 9001000px 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 descendantsbackdrop-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.scss
  • markdown.scss, mentions.scss, table.scss
  • fab-wrapper.scss, wrap-buttons.scss, multi-btn-wrapper.scss
  • planner-shared.scss, formly-rows.scss, scrollbars.scss