Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Style System

The application style system is a shared Obsidian-aligned theme layered over shadcn-svelte components and first-party plugin CSS. The goal is a note-app interface that feels close to Obsidian’s restrained dark product chrome while preserving the existing Svelte component structure, light-mode support, and plugin ownership boundaries.

Ownership

@lapis-notes/ui/theme.css is the canonical palette and Tailwind token bridge entrypoint. It imports grouped partials under src/lib/styles/theme/ for the light palette, dark overrides, semantic aliases, shared foundations, document/content roles, shared surfaces, and Tailwind bridge tokens. Together they define the shared shadcn/Tailwind variables (--background, --card, --popover, --primary, --border, --ring, etc.), the Obsidian-compatible aliases that runtime and plugin code consume (--background-primary, --background-primary-alt, --background-secondary, --background-modifier-border, --text-normal, --text-muted, --text-faint, --interactive-accent, --text-on-accent, etc.), and the shared compatibility helpers that still bridge older Obsidian-style CSS (--color-base-*, --color-accent-*, --mono-rgb-*, shared selection and shadow helpers, the blur/raised surface chain, and related form-field hover helpers).

The workspace workspace-shell.css file owns app-specific Obsidian shell variables and selectors for tabs, stacked tabs, ribbons, status bar, and layout behavior. It should consume shared theme tokens instead of redefining the global palette.

CodeMirror 6 autocomplete tooltip surfaces (.cm-tooltip-autocomplete, completion info) are owned by @lapis-notes/ui/codemirror-autocomplete.css (exported beside theme.css). CodeMirror lint chrome is workspace-owned in packages/workspace/src/lib/styles/codemirror-lint.css because the workspace app is the runtime that loads editor shell CSS; it covers lint ranges, gutter markers, the Lapis-owned hover tooltip shell, the lint panel, and the inline View Problem widget while consuming shared UI theme tokens.

First-party plugins own plugin-prefixed selectors and variables in their package app.css files. Plugin variables should default to shared core variables and only introduce plugin-specific roles for real semantic states such as task status, due dates, trace status, graph categories, warnings, errors, or success states.

Effective Tokens

Dark mode is the primary target:

RoleToken examplesEffective value
Canvas--background, --background-primary#1e1e1e
Surface--card, --background-primary-alt#242424
Raised/secondary surface--secondary, --muted, --sidebar, --background-secondary#262626
Hairline--border, --background-modifier-border, --sidebar-border#363636
Strong/input border--input, --sidebar-accent, --background-modifier-border-hover#3f3f3f
Neutral hover--background-modifier-hover, sidebar rows use --sidebar-accenttranslucent
Neutral selected--background-modifier-active-hoveraccent tint
Primary text--foreground, --text-normal#dadada
Muted text--muted-foreground, --text-muted#b3b3b3
Accent text/focus--text-accent, --ring#a78bfa
Filled interactive--interactive-accent, --primary bridgeconfigured accent
Pressed interactive--interactive-accent-presseddarker accent

Light mode remains supported as a restrained structural mirror. Dark mode should be treated as the visual source of truth when choosing new token mappings.

Theme compatibility must continue to work for both class families, dark/light and theme-dark/theme-light, whether they are applied on html or body. Shared theme partials own that selector compatibility; plugin CSS should not work around it with duplicate palette blocks.

Existing font imports remain in place. The current implementation does not adopt a no-web-font rule.

Component Language

Shared UI components should use semantic Tailwind tokens and compact Obsidian-like chrome:

  • Prefer hairline borders and surface changes over shadows.
  • Neutral controls use --background-modifier-hover for hover state and --background-modifier-active-hover for selected or toggled state. Sidebar row-style controls use --sidebar-accent for hover and active row surfaces.
  • Prefer 4-8px radii for cards, inputs, menus, dialogs, table containers, and repeated rows.
  • Reserve pill geometry for controls that are naturally pill-shaped, such as switches, chips, badges, and compact status markers.
  • Use violet sparingly for links, focus, selected state, and filled primary controls.
  • Runtime components and plugin CSS should consume semantic roles such as --interactive-accent, --interactive-accent-hover, --text-accent, and --text-on-accent; raw --accent-interactive tokens are theme implementation details except where the Tailwind bridge exposes utility aliases.
  • Use semantic state colors only for status meaning: destructive/error, warning/due, success/completed, graph/category, and telemetry trace state.

Workspace chrome follows the same language: tabs, sidebars, status bar, command palette, settings, startup panels, editor tooltips, and toasts should inherit shared variables and avoid introducing standalone palettes.

Sidebar chrome is placement-aware. A leaf or tab group moved into a left or right sidedock should inherit sidebar surface and boundary roles from workspace-shell.css even if the mounted view uses the main --background-primary canvas internally. Dock outer borders, internal pane separators, tab/header borders, grouped-panel borders, and collapsed-sidebar rails should remain visible against both sidebar and editor backgrounds.

Accent Usage

Obsidian treats the accent color as an attention cue for interactive elements, not as a general decoration color. In this codebase:

  • Use --interactive-accent for filled primary actions, selected checkboxes or switches, active resize handles, and other controls whose active state needs a strong user-action signal.
  • Pair any filled --interactive-accent background with --text-on-accent. Do not assume --primary-foreground or white remains readable after user theme changes.
  • Use --interactive-accent-hover and --interactive-accent-pressed only for hover/pressed states of controls that already use --interactive-accent.
  • Use --text-accent for inline links, linked file names, focused status affordances, and small text/icon cues that should read as navigable or actionable without becoming filled controls.
  • Use --ring or --background-modifier-border-focus for focus outlines. Do not replace keyboard focus indicators with a background-only accent.
  • Use --background-modifier-hover for neutral hover rows, toolbar buttons, menus, and list items. Hover alone is not enough reason to use violet.
  • Use --background-modifier-active-hover for selected or toggled neutral controls when a filled accent would overstate the state.
  • Use --sidebar-accent for sidebar row hover/active surfaces unless the row is a primary action or selected control that needs the stronger accent treatment.
  • Use low-opacity accent mixes, such as color-mix() or /10 utility forms, only for drop targets, search/tag highlights, table selection guides, and other temporary interaction hints.
  • Avoid raw --accent-interactive in component and plugin CSS. It is a theme implementation token; use the semantic aliases above.

Plugin Inheritance

Plugins should not depend on workspace-global selectors for plugin chrome. They should expose plugin-prefixed selectors and variables while inheriting core theme roles:

  • Surface roles default to --background-primary, --background-primary-alt, --card, or sidebar tokens for sidebar-hosted views.
  • Border roles default to --background-modifier-border, --border, or --input.
  • Text roles default to --text-normal, --text-muted, and --text-faint.
  • Accent roles default to --interactive-accent, --text-accent, and --text-on-accent.
  • Error, warning, and success roles default to --text-error, --text-warning, and --text-success when present.

Standalone plugin builds continue to include @lapis-notes/ui/theme.css through the plugin CSS contract so plugin-local testing and native-style installs see the same effective token system.

Plugins should consume shared compatibility tokens for common Obsidian-derived roles such as callout palette values, table selection, raised surfaces, and form-field surfaces. When a plugin needs its own semantic colors, it should derive local aliases from the shared state tokens instead of re-declaring theme-light and theme-dark palette ramps.

Change Rules

Style changes that alter the global palette, semantic variable meanings, component shape language, plugin inheritance rules, or dark/light behavior must update this page. Package-local styling behavior should also update the owning package page or package-local spec.md when it changes durable behavior.