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

Plugin CSS Contract

First-party packaged plugins need two CSS entry points so they can run inside the workspace without duplicating Tailwind output and still remain self-contained when installed or tested as native Obsidian-style plugins.

This contract applies to first-party packages under packages/plugins/ that ship UI. Community plugins continue to follow the Obsidian-compatible plugin folder contract documented in Plugin Runtime.

The shared palette, shape language, and plugin inheritance rules that plugin CSS must consume are documented in Style System. This page owns package artifact and selector rules.

Bundle Contract

Each first-party plugin that needs styling should emit these CSS artifacts:

ArtifactConsumerContents
dist/app.cssWorkspace app bundlePlugin-owned BEM/custom rules only. It must not include Tailwind preflight, Tailwind theme output, or generated utility rules.
dist/styles.cssStandalone/native plugin installs and plugin-local testingSelf-contained CSS with the reusable UI theme, full Tailwind output generated from the plugin source scan, and the plugin app.css rules.

The workspace is the only full Tailwind app entry. Workspace core-plugin registration provides bundled plugin app.css content to PluginManager as bundled lifecycle CSS, never plugin standalone styles.css files. In local workspace development, those inline CSS imports point at bundled plugin src/app.css entries so style edits reload from source; published package consumers continue to use the exported dist/app.css artifact. External official plugins are not scanned or imported by the default workspace bundle; when installed, they load packaged styles.css from /.obsidian/plugins/<id>/ through the normal plugin CSS lifecycle.

Standalone or native plugin installs use styles.css so the plugin can render outside the workspace without relying on host-provided Tailwind output. For first-party plugin development, @lapis-notes/ui/styles.css provides the full UI standalone CSS used to preview plugin-local UI, while @lapis-notes/ui/theme.css provides reusable core variables and Tailwind theme mappings.

Selector Contract

First-party plugin custom CSS uses additive BEM hooks. Tailwind utility classes may continue to express layout and spacing in Svelte components, but plugin-owned selectors are the stable override surface for host styles, tests, and downstream plugin-native installs.

Selector names must use the plugin namespace as the prefix:

  • plugin-markdown uses markdown-...
  • plugin-notebook uses notebook-...
  • plugin-search uses search-...
  • plugin-bases uses bases-...
  • plugin-graph uses graph-...
  • plugin-pdf uses pdf-...
  • plugin-canvas uses canvas-...
  • plugin-slides uses slides-...
  • plugin-telemetry uses telemetry-...

Plugin CSS should not rely on workspace-global selectors for plugin chrome. When a shared UI component needs styling, prefer the shared UI component API and semantic Tailwind tokens before adding plugin-local selectors.

Tailwind Versus App CSS

Use app.css for plugin-owned styling contracts. Use Tailwind classes in component markup for local layout and composition.

Plugin-owned production CSS must be externalized into the package stylesheet artifacts rather than kept in component-local Svelte <style> blocks. Runtime-dependent values may still be passed from markup or script through CSS custom properties or other JS-owned style properties when the external stylesheet consumes them, but the styling rules themselves belong in app.css and the derived standalone styles.css artifact.

Put styles in app.css when they are:

  • stable override hooks for hosts, tests, or plugin authors
  • plugin-specific visual semantics such as surfaces, borders, warning states, error states, selected states, or accents
  • repeated across multiple plugin components
  • based on plugin-prefixed CSS variables
  • replacing hard-coded color utilities in plugin-owned chrome
  • required in both the workspace build and standalone/native plugin build
  • clearer as a named semantic rule than as repeated or brittle utility strings

Keep Tailwind utilities in Svelte markup when they are:

  • spacing, sizing, and local geometry such as px-3, gap-2, h-8, or w-full
  • layout and flow such as flex, grid, items-center, min-w-0, or overflow-auto
  • local typography scale or weight that is not plugin-specific
  • simple one-off composition that does not need to be themed, reused, or overridden

A component may combine both. The BEM class owns the semantic styling and Tailwind owns the local layout:

<div class="notebook-cell__surface flex flex-col rounded-md border px-3 py-2">

In that example, notebook-cell__surface owns background, border color, text color, and state variants through plugin variables. The Tailwind utilities arrange the element and set non-semantic geometry.

Variable Contract

Plugin-specific CSS variables must use the plugin namespace as the prefix, for example --notebook-surface-background or --notebook-accent.

Plugins should prefer core theme variables before introducing plugin variables. When plugin variables are useful, they should default to core theme variables so Obsidian/native hosts and the workspace can override one theme surface consistently:

  • --background-primary
  • --background-primary-alt
  • --background-modifier-border
  • --background-modifier-hover
  • --text-normal
  • --text-muted
  • --text-faint
  • --interactive-accent
  • --text-on-accent

@lapis-notes/ui/theme.css is the canonical source for those core variables. It maps them to the Obsidian-aligned dark palette and keeps light-mode counterparts available, so plugin packages should not duplicate the global canvas/surface/text/accent palette in package CSS. Plugin-specific state colors, such as warnings, errors, success, due-date states, trace status, and graph/category colors, may wrap global semantic variables (--text-warning, --text-error, --text-success, --interactive-accent) when those states need to remain distinct.

Sidebar-hosted plugin surfaces should prefer sidebar theme variables before generic background and text variables:

  • --sidebar
  • --sidebar-foreground
  • --sidebar-border
  • --sidebar-accent
  • --sidebar-accent-foreground
  • --sidebar-primary
  • --sidebar-primary-foreground
  • --sidebar-ring

Plugin variables may wrap these core variables when a plugin has repeated semantic roles such as warning surfaces, cell borders, output backgrounds, or graph accents. Hard-coded color utilities should be avoided in plugin-owned chrome when a core variable or plugin-prefixed variable can express the same role.

Implemented Slices

plugin-notebook is the first implementation slice for this contract.

  • Notebook is external official in the default app bundle; installed lapis-notebook loads packaged styles.css from /.obsidian/plugins/lapis-notebook/.
  • @lapis-notes/notebook/app.css exports notebook BEM/custom rules without Tailwind output.
  • @lapis-notes/notebook/styles.css exports standalone notebook CSS built from UI theme/full Tailwind output plus notebook source scanning and notebook addon rules.
  • Notebook-owned wrapper classes cover the notebook view, execution banner, cells, outputs, dataframe viewer, inspector panel, dependencies panel, and packages panel.
  • Notebook plugin variables default to the core theme variables listed above.

The notebook package keeps Tailwind utilities for spacing and layout where they are still the clearest expression, while BEM classes provide stable override hooks for notebook-owned surfaces.

plugin-search is the second implementation slice for this contract.

  • Workspace core-plugin registration provides packages/plugins/plugin-search/src/app.css to PluginManager as bundled lifecycle CSS during local development.
  • @lapis-notes/search/app.css exports search BEM/custom rules without Tailwind output.
  • @lapis-notes/search/styles.css exports standalone search CSS built from UI theme/full Tailwind output plus search source scanning and search addon rules.
  • Search-owned wrapper classes cover the search view, query editor, settings panel, result groups, match rows, highlighted query segments, and semantic-search status popover trigger.
  • Search plugin variables use the --search-... prefix and default to sidebar theme variables first, then generic core theme variables, for surfaces, borders, text, accents, highlights, status tones, and warning states.

plugin-bases is the third implementation slice for this contract.

  • Workspace core-plugin registration provides packages/plugins/plugin-bases/src/app.css to PluginManager as bundled lifecycle CSS during local development.
  • @lapis-notes/bases/app.css exports bases BEM/custom rules without Tailwind output.
  • @lapis-notes/bases/styles.css exports standalone bases CSS built from UI theme/full Tailwind output plus bases source scanning and bases addon rules.
  • Bases-owned wrapper classes cover the bases view shell, toolbar, search panel, table scroller, table head, rows, cells, footer summaries, card/list surfaces, card placeholders, and unknown-view error state.
  • Bases plugin variables use the --bases-... prefix and default to core theme variables for surfaces, borders, text, accents, cards, table focus state, and error state.

plugin-graph is the fourth implementation slice for this contract.

  • Graph is external official in the default app bundle; installed lapis-graph loads packaged styles.css from /.obsidian/plugins/lapis-graph/.
  • @lapis-notes/graph/app.css exports graph view and graph controls BEM/custom rules without Tailwind output.
  • @lapis-notes/graph/styles.css exports standalone graph CSS built from UI theme/full Tailwind output plus graph source scanning and graph control rules.
  • Graph-owned wrapper classes cover the graph view shell, canvas surface, controls toolbar, settings panel, and graph settings inputs.
  • Graph plugin variables use the --graph-... prefix and default to core theme variables for surfaces, borders, controls, and input states.

plugin-canvas is the fifth implementation slice for this contract.

  • Canvas is external official in the default app bundle; installed lapis-canvas loads packaged styles.css from /.obsidian/plugins/lapis-canvas/.
  • @lapis-notes/canvas/app.css exports canvas BEM/custom rules without Tailwind output.
  • @lapis-notes/canvas/styles.css exports standalone canvas CSS built from UI theme/full Tailwind output plus XYFlow base CSS and canvas source scanning.
  • Canvas-owned wrapper classes cover the canvas view shell, alignment guides, nodes, edges, toolbars, label editors, and error state.
  • Canvas plugin variables use the --canvas-... prefix and default to core theme variables for surfaces, borders, node fills, accents, toolbars, and connector states.

plugin-slides is the sixth implementation slice for this contract.

  • Slides is external official in the default app bundle; installed lapis-slides loads packaged styles.css from /.obsidian/plugins/lapis-slides/.
  • @lapis-notes/slides/app.css exports slides-owned rules without Tailwind output, while Reveal base CSS remains a plugin addon dependency.
  • @lapis-notes/slides/styles.css exports standalone slides CSS built from UI theme/full Tailwind output plus Reveal base CSS and slides source scanning.
  • Slides-owned wrapper classes cover the fullscreen overlay, presentation viewport, close button, and rendered markdown slide surfaces.
  • Slides plugin variables use the --slides-... prefix for plugin-specific colors and map those values onto Reveal’s runtime theme variables.

plugin-telemetry is the seventh implementation slice for this contract.

  • Telemetry is external official in the default app bundle; installed lapis-telemetry loads packaged styles.css from /.obsidian/plugins/lapis-telemetry/.
  • @lapis-notes/telemetry/app.css exports telemetry BEM/custom rules without Tailwind output.
  • @lapis-notes/telemetry/styles.css exports standalone telemetry CSS built from UI theme/full Tailwind output plus telemetry source scanning.
  • Telemetry-owned wrapper classes cover the diagnostics root, summary cards, table shells, waterfall rows, trace header, span details panel, measurement chips, and settings tab wrapper.
  • Telemetry plugin variables use the --telemetry-... prefix and default to core theme variables for surfaces, borders, accent states, warning states, and settings cards.

plugin-markdown is the eighth implementation slice for this contract.

  • Workspace core-plugin registration provides packages/plugins/plugin-markdown/src/app.css to PluginManager as bundled lifecycle CSS during local development.
  • @lapis-notes/markdown/app.css exports markdown-owned rules without Tailwind app output, including markdown preview and sidebar view shell wrappers plus markdown-specific CodeMirror, rendered-markdown, and callout CSS split by area under src/styles/.
  • @lapis-notes/markdown/styles.css exports standalone markdown CSS built from UI theme/full Tailwind output plus markdown source scanning and markdown addon rules.
  • Markdown-owned wrapper classes cover the editor shell, reading view shell, rendered markdown document, media view, outline view, file properties view, and all properties view.
  • Markdown CSS separates shared surface/rendered-content rules from source-mode, live-preview, reading-mode, and embedded-mode overrides, with stable hooks for each mode so selector changes do not leak across markdown surfaces.
  • Markdown plugin variables use the --markdown-... prefix and default to core theme or sidebar theme variables for document surfaces, sidebar surfaces, borders, text, accent states, and editor-specific markdown tokens.

plugin-pdf is the ninth implementation slice for this contract.

  • PDF is external official in the default app bundle; installed lapis-pdf loads packaged styles.css from /.obsidian/plugins/lapis-pdf/.
  • @lapis-notes/pdf/app.css exports PDF view and embed BEM/custom rules without Tailwind output.
  • @lapis-notes/pdf/styles.css exports standalone PDF CSS built from UI theme/full Tailwind output plus PDF source scanning.
  • PDF-owned wrapper classes cover the file view shell, viewer host, runtime mount container, and markdown embed surface.
  • PDF plugin variables use the --pdf-... prefix and default to core theme variables for shell backgrounds, viewer surfaces, borders, text, muted text, and error states.
  • PDF stylesheet availability follows the same bundled plugin lifecycle path as the other first-party plugin styles, so clean workspace sessions no longer depend on a separate global workspace stylesheet import for PDF chrome.

plugin-docs is the tenth implementation slice for this contract.

  • Docs is no longer registered by the workspace core-plugin bootstrap. Installed Docs loads styles.css from /.obsidian/plugins/lapis-docs/ through the normal community plugin CSS lifecycle.
  • @lapis-notes/docs/app.css exports Docs-owned rules without Tailwind app output.
  • @lapis-notes/docs/styles.css exports standalone Docs CSS built from UI theme/full Tailwind output plus Docs source scanning and Univer preset CSS.
  • Docs-owned wrapper classes cover the document and sheet view shell, Univer canvas host, load/save/error messages, view action buttons, and status-bar fallback controls.
  • Docs plugin variables use the --docs-... prefix and default to core theme variables for editor surfaces, borders, text, accent, warning, and error states.

Migration Tracker

This tracker covers first-party packaged plugins under packages/plugins/. It is narrower than the older “plugin-owned styling” status: a plugin is complete here only after it follows the two-artifact CSS contract, exposes stable plugin-prefixed BEM hooks for owned UI surfaces, and uses plugin-prefixed variables for plugin-specific styling.

PluginStatusNotes
plugin-notebookImplemented first sliceEmits app.css and styles.css, exposes notebook BEM hooks, and defaults notebook variables to core theme variables.
plugin-searchImplementedEmits app.css and styles.css, exposes search BEM hooks, and defaults search variables to core theme variables.
plugin-basesImplementedEmits app.css and styles.css, exposes bases BEM hooks, and defaults bases variables to core theme variables.
plugin-graphImplementedEmits app.css and styles.css, exposes graph BEM hooks for the graph view and controls, and defaults graph variables to core theme variables.
plugin-canvasImplementedEmits app.css and styles.css, keeps XYFlow base CSS in standalone output, and defaults canvas variables to core theme variables.
plugin-slidesImplementedEmits app.css and styles.css, keeps Reveal base CSS in standalone output, and maps --slides-... variables onto Reveal theme variables.
plugin-telemetryImplementedEmits app.css and styles.css, exposes telemetry BEM hooks for diagnostics and settings surfaces, and defaults telemetry variables to core theme variables.
plugin-markdownImplementedEmits app.css and styles.css, exposes markdown-prefixed wrapper hooks for editor, preview, embedded, media, outline, and properties surfaces, and defaults markdown variables to core theme variables.
plugin-pdfImplementedEmits app.css and styles.css, exposes pdf-... wrapper hooks and --pdf-... variables, and now loads as the registry-installed lapis-pdf plugin instead of workspace bootstrap CSS.
plugin-docsImplementedEmits app.css and styles.css, exposes docs-... wrapper hooks and --docs-... variables, and now loads as the registry-installed lapis-docs plugin instead of workspace bootstrap CSS.
plugin-spellcheckerOut of scopeObsidian-targeted/community-style plugin package, not part of this first-party CSS tracker.

Update this table as each plugin completes the contract. Do not advance another first-party plugin migration without updating this page in the same change.