Skip to content

Latest commit

 

History

History
395 lines (326 loc) · 17.7 KB

ui-patterns.md

File metadata and controls

395 lines (326 loc) · 17.7 KB

UI patterns

Use this reference for commands, views, settings UI, modals, menus, DOM, styling, accessibility, and framework-mounted interfaces. Confirm APIs and CSS variables against installed declarations, the official reference, and manifest.minAppVersion.

Contents

Choose the UI surface

Need Preferred surface
Conditionally expose an action Command with checkCallback or editorCheckCallback
Show persistent plugin UI ItemView with a stable view type
Collect a short interaction Modal, SuggestModal, or FuzzySuggestModal
Add contextual file/editor actions Menu through workspace menu events
Configure durable values PluginSettingTab appropriate to the minimum app version

Avoid a custom view when a command, modal, or menu is sufficient.

Commands and entry points

  • Keep command IDs stable across releases. Obsidian already prefixes them with the plugin ID, so do not repeat that ID.
  • Use sentence case for names. Do not include the plugin name when Obsidian already supplies context.
  • Avoid default hotkeys; let users choose conflict-free bindings.
  • In a checkCallback, perform no side effects while checking is true. Repeat the same validity check before executing when it is false.
  • Prefer editorCallback for operations that require an active Markdown editor.
  • A ribbon icon is optional and user-hideable. Always provide a command or another discoverable route; do not add a separate ribbon-visibility setting.
  • Status bar items are desktop-only UI even when the rest of the plugin supports mobile.

Views and workspace

  • Register a stable view type and construct a fresh ItemView in the factory. Do not store the constructed view on the plugin.
  • Use workspace queries such as getLeavesOfType() or documented active-view helpers when access is needed.
  • Render in contentEl and clean owned UI in onClose(). Do not detach leaves in plugin onunload(); preserving leaf placement allows Obsidian to restore views during plugin updates.
  • Treat leaf.view as potentially deferred on supported app versions. Check DeferredView before casting. Prefer revealLeaf() when the user is about to see it; call loadIfDeferred() only when background loading is truly necessary and compatibility is guarded.
  • Use onLayoutReady() for restored-layout work and startup UI that depends on initialized workspace state.
  • For pop-out windows, derive the owning window and document from the element (element.win / element.doc), use Obsidian cross-window instanceOf helpers where required, and handle window migration for long-lived views.
  • Use activeWindow or activeDocument only when the intended target is whichever window currently has focus.

Settings APIs

Choose the implementation from minAppVersion:

Compatibility target Implementation
>= 1.13.0 Prefer declarative getSettingDefinitions()
< 1.13.0, but new declarative behavior is valuable Keep display() and add a synchronized declarative definition
< 1.13.0, no need for declarative features Keep the imperative display() implementation

For declarative settings:

  • Keep getSettingDefinitions() cheap and deterministic. Obsidian calls it repeatedly and during search indexing; do no I/O or network work there.
  • Prefer built-in control definitions so Obsidian binds and saves top-level settings automatically.
  • Use visible and disabled predicates for conditional behavior. Use render only for custom controls, derived values, or side effects.
  • A render callback must save mutations explicitly and return cleanup for long-lived resources.
  • Treat validate as a UI gate, not a data migration. Revalidate and repair stored values during settings loading.
  • Call update() after interdependent state or list changes. In dual-support mode, update both implementations together.
  • Keep page names unique among siblings and use stable row data rather than captured positional indexes for actions.

For all settings UIs:

  • Use sentence case. Do not add a top-level heading; add section headings only when multiple sections exist, and do not put “settings” or “options” in headings.
  • Use Setting#setHeading() rather than raw heading elements.
  • Save on change. Keep one primary control per row so it can stack on mobile.
  • Keep descriptions concise and put destructive confirmation in a modal.

Modals, menus, HTML, and icons

  • Use Modal with Setting for small structured interactions; use SuggestModal or FuzzySuggestModal for searchable choices.
  • Build context menus with Menu and registered workspace events such as file or editor menus.
  • Build DOM with createEl, createDiv, createSpan, and related helpers. Never inject user or vault content with innerHTML, outerHTML, or insertAdjacentHTML; clear with empty().
  • Use setIcon() and the bundled Lucide set. Add a custom icon only when necessary and follow Obsidian/Lucide geometry and naming guidance.
  • Prefer Notice for brief, actionable feedback. Keep errors specific without revealing secrets or private note content.

Obsidian-native styling

Prefer Obsidian's public UI APIs, documented design tokens, and interaction conventions before introducing plugin-specific equivalents.

  • Use documented semantic CSS variables such as --background-primary, --background-secondary, --background-modifier-border, --background-modifier-hover, --text-normal, --text-muted, --interactive-normal, --interactive-hover, and --interactive-accent instead of hardcoded colors.
  • Prefer public Obsidian UI components such as Setting, ButtonComponent, TextComponent, DropdownComponent, ToggleComponent, Modal, Menu, Notice, and setIcon() when they provide the required behavior.
  • Do not depend on undocumented Obsidian DOM structure or internal CSS class names. Use plugin-owned classes for custom structure, state, and styling.
  • Start with semantic HTML and Obsidian component APIs. Add custom controls only when native components cannot express the required interaction.
  • Put presentation in styles.css under a plugin-owned root class; do not assign hardcoded visual styles from JavaScript.
  • Keep plugin CSS focused on layout, hierarchy, visualization, and product-specific components rather than replacing Obsidian's entire visual foundation.
  • Preserve light and dark theme compatibility and allow community themes to influence the plugin through semantic variables.
  • Scope deliberately branded surfaces under a plugin-owned root class. Retain accessible contrast, visible focus, reduced-motion behavior, and usable interaction states in both light and dark themes.
  • Derive custom colors, typography, borders, focus states, and interactive states from documented Obsidian variables wherever practical.
  • Prefer the curated variable set below and logical CSS properties such as margin-inline-start so zoom, density, and right-to-left layouts remain coherent. Treat --size-* as a family shorthand only; use a concrete documented variable in CSS.
  • Test [dir="rtl"], .mod-rtl, mixed-direction note text, and icons whose direction carries meaning.
  • Verify any CSS variable against the current official reference and the plugin's declared minAppVersion before relying on it.

Recommended CSS variable set

Use var(--token) rather than copying default values. This list is a practical plugin-author subset of Obsidian's documented variables; it is not an invitation to redefine theme tokens globally. Popularity alone is not evidence that a token is public: plugin-private names from Calendar, Kanban, or theme snippets are intentionally excluded.

Surface

--background-primary
--background-primary-alt
--background-secondary
--background-secondary-alt
--background-modifier-hover
--background-modifier-active-hover
--background-modifier-border
--background-modifier-border-hover
--background-modifier-border-focus
--background-modifier-form-field
--background-modifier-error
--background-modifier-error-hover
--background-modifier-success
--background-modifier-message

Text

--text-normal
--text-muted
--text-faint
--text-accent
--text-accent-hover
--text-on-accent
--text-on-accent-inverted
--text-success
--text-warning
--text-error
--text-selection
--text-highlight-bg
--caret-color

Interaction

--interactive-normal
--interactive-hover
--interactive-accent
--interactive-accent-hover

Do not add --interactive-accent-hsl to new code; the official reference marks it as deprecated and retained for theme compatibility. Prefer the non-HSL token and modern color functions when a derived color is required.

Semantic colors

--color-red
--color-orange
--color-yellow
--color-green
--color-cyan
--color-blue
--color-purple
--color-pink

Accent controls

--accent-h
--accent-s
--accent-l
--color-accent
--color-accent-1
--color-accent-2

Treat these as theme-controlled values for accent-derived decoration, not as plugin settings. Prefer the semantic accent variables over manually combining HSL channels; do not hardcode light/dark accent values or redefine the user's accent.

Typography

--font-interface-theme
--font-text-theme
--font-monospace-theme
--font-text-size
--font-ui-smaller
--font-ui-small
--font-ui-medium
--font-ui-large
--line-height-normal
--line-height-tight
--font-weight
--font-smallest
--font-smaller
--font-small
--font-normal
--font-medium
--font-semibold
--font-bold
--font-extrabold
--heading-spacing
--p-spacing

Use fixed --font-ui-* tokens for plugin chrome and relative --font-* tokens for editor-like content. Use the documented weight helpers instead of inventing numeric font variables. Avoid setting a plugin-wide font family or size that defeats the user's Appearance settings.

Do not infer legality from a plugin stylesheet: some plugins reference ambient names such as --font-monospace, but the current official reference documents --font-monospace-theme instead. Prefer the documented token unless the target Obsidian version explicitly guarantees another variable.

Geometry and spacing

--border-width
--radius-s
--radius-m
--radius-l
--radius-xl
--size-4-1
--size-4-2
--size-4-3
--size-4-4
--size-4-5
--size-4-6
--size-4-8
--size-4-9
--size-4-12
--size-4-16
--size-4-18

Use the 4-pixel grid for normal layout. The documented --size-2-1, --size-2-2, and --size-2-3 tokens are available for fine adjustments, but should be used sparingly.

Stacking and overlays

--layer-cover
--layer-sidedock
--layer-status-bar
--layer-popover
--layer-slides
--layer-modal
--layer-notice
--layer-menu
--layer-tooltip
--layer-dragged-item

Use a documented layer only when the surface has the corresponding semantic role. Do not invent arbitrary z-index values or override Obsidian's global layer scale.

Icons

--icon-size
--icon-stroke
--icon-color
--icon-color-hover
--icon-color-active
--icon-color-focused
--icon-opacity
--icon-opacity-hover
--icon-opacity-active
--clickable-icon-radius
--icon-xs
--icon-s
--icon-m
--icon-l
--icon-xl

Prefer setIcon() and Lucide icons; use these tokens when a plugin-owned icon wrapper needs to align with Obsidian's icon sizing, stroke, or interaction states.

Native component tokens

Use component tokens only for the matching native surface; do not use them as general-purpose replacements for semantic colors or spacing.

--button-radius
--input-height
--input-radius
--input-font-weight
--input-border-width
--toggle-border-width
--toggle-width
--toggle-radius
--modal-background
--modal-border-width
--modal-border-color
--modal-radius
--nav-item-size
--nav-item-color
--nav-item-color-hover
--nav-item-color-selected
--nav-item-background-hover
--nav-item-background-selected
--nav-item-padding

The component pages define additional specialized tokens. Add one here only when a plugin repeatedly styles that native component and the token is documented for the target minAppVersion.

Tables and editor-rendered content

Use these when a plugin renders a table or intentionally matches Obsidian's editor table surface. Dataview's public stylesheet, for example, uses --table-row-background-hover together with Obsidian's background, text-selection, and navigation tokens.

--table-background
--table-border-width
--table-border-color
--table-header-background
--table-header-background-hover
--table-header-border-width
--table-header-border-color
--table-header-color
--table-line-height
--table-text-size
--table-text-color
--table-row-background-hover
--table-row-alt-background
--table-row-alt-background-hover
--table-selection
--table-selection-border-color
--table-selection-border-width
--table-selection-border-radius

Do not assume these tokens apply to arbitrary cards or lists; scope them to a plugin-owned table root or a matching editor-rendered surface.

Checkboxes and task surfaces

These tokens are useful for task views, checklist renderers, and controls that intentionally match Obsidian's native checkbox appearance.

--checkbox-size
--checkbox-radius
--checkbox-marker-color
--checkbox-color
--checkbox-color-hover
--checkbox-border-color
--checkbox-border-color-hover
--checklist-done-decoration
--checklist-done-color

Dividers

--divider-color
--divider-color-hover
--divider-width
--divider-width-hover
--divider-vertical-height

Use divider tokens for separators and resize affordances; preserve the semantic direction and interactive state rather than substituting a hardcoded border color.

Framework UI lifecycle

  • Mount React or Svelte only into an element owned by the plugin or view.
  • Unmount React roots and Svelte components in the matching onClose() or component cleanup.
  • Pass App and services as dependencies or context; do not use global app.
  • Keep Obsidian CSS variables and interaction conventions inside framework components.

Accessibility and responsive behavior

  • Give icon-only controls an accessible label and preserve visible focus.
  • Keep native keyboard behavior and tab order; do not make a clickable div when a button or link is appropriate.
  • Announce meaningful asynchronous status and errors without creating noisy repeated notices.
  • Respect prefers-reduced-motion and do not encode state by color alone.
  • Test long localized text, zoom, empty/loading/error/disabled states, narrow panes, pop-out windows, touch targets, and software-keyboard overlap.
  • Verify light and dark themes plus at least one community theme when custom CSS is substantial.

Official references