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

UI Package (@lapis-notes/ui)

The UI package is the shared design-system layer. It wraps headless component libraries (Bits UI, paneforge, svelte-sonner) in Tailwind-styled Svelte components that the workspace shell and API-level components share.

Source: packages/ui/src/lib/

Design Principles

  • Presentation only — no app state, workspace behavior, plugin lifecycle, storage, or metadata.
  • Thin wrappers over headless primitives with consistent Tailwind styling.
  • Variant-based APIs using tailwind-variants and class-variance-authority.
  • Composable: most complex components are exported as namespaced sub-components (e.g., Dialog.Root, Dialog.Content).
  • Plugin-specific chrome, selector namespaces, and stylesheet ownership stay in the owning plugin package. Shared UI components should remain generic building blocks instead of accumulating plugin-specific override rules.
  • Theme-first styling: shared components consume theme.css semantic variables and prefer hairline borders, 4-8px radii, visible neutral hover states through --background-modifier-hover, selected neutral states through --background-modifier-active-hover, and minimal shadows so workspace and plugin chrome inherit the same Obsidian-aligned surface system.

Theme Tokens

theme.css is the canonical shared palette and Tailwind token bridge entrypoint. Internally it imports grouped partials from src/lib/styles/theme/ for the light palette, dark overrides, semantic aliases, shared foundations, document/content roles, shared surfaces, and Tailwind bridge tokens. Dark mode is the primary target and follows the attached Obsidian default ladder: #1e1e1e background, #242424 card/alternate surface, #262626 secondary/sidebar surfaces, #363636 hairline/sidebar borders, #3f3f3f strong/input borders and sidebar hover surfaces, #dadada text, and #b3b3b3 muted text. Accent roles resolve through --color-accent*; components use semantic --interactive-accent, --text-accent, and --text-on-accent instead of raw --accent-interactive.

Light mode remains supported as a restrained structural mirror. Existing font imports and the mono font stack remain unchanged.

The shared theme continues to expose shadcn/Tailwind variables (--background, --card, --primary, --border, etc.), Obsidian-compatible aliases (--background-primary, --background-modifier-border, --text-normal, --text-muted, --interactive-accent, --text-on-accent), and compatibility helpers (--color-base-*, --color-accent-*, --mono-rgb-*, theme-level selection and shadow tokens, the blur/raised surface chain, and form-field hover helpers) so workspace chrome, first-party plugins, and community-style plugin CSS inherit a single token source.

Theme ownership also includes class-family compatibility: the shared token layer must resolve correctly for both dark/light and theme-dark/theme-light classes when the runtime applies them on either html or body.

The cross-package Style System page is the durable contract for effective tokens, shape language, plugin inheritance, and update rules.

Utilities

  • cn(...inputs) — Class merging via clsx + tailwind-merge.
  • fuzzySearch(items, query, { keys }) — Shared Fuse.js-backed fuzzy ranking helper for batch filtering when Command.Root uses shouldFilter={false}.
  • fuzzyMatchScore(value, query, keywords?) / createFuzzyMatchScore() — Shared per-item Fuse scorer for menus, tags, metadata autocomplete, and other ranked filter lists.
  • commandFuzzyFilter / createCommandFuzzyFilter() — Aliases of the per-item scorer for bits-ui Command.Root filter props.
  • WithoutChild<T>, WithoutChildren<T>, WithoutChildrenOrChild<T> — Type utilities for removing slot props.
  • WithElementRef<T, U> — Adds bindable ref property.

Hooks

IsMobile

IsMobile extends MediaQuery. Constructor takes breakpoint (default 768px). Provides reactive mobile detection.

Component Catalog

Layout Components

ComponentBacking LibrarySub-componentsKey Props
Accordionbits-uiRoot, Item, Trigger, Contentvalue, onValueChange
Collapsiblebits-uiRoot, Trigger, Contentopen (bindable)
ResizablepaneforgePaneGroup, Pane, Handledirection; Handle keeps a constant hit area and thickens its visible separator line from 2px to 4px in var(--interactive-accent) on hover/focus-visible
Sidebarcustom25+ sub-componentsopen, side, variant, collapsible; Rail uses the same hover/focus accent-line treatment
Sheetbits-ui DialogRoot, Trigger, Overlay, Content, Header, Title, Footer, Closeside (“top”/“bottom”/“left”/“right”)
CardcustomRoot, Header, Title, Description, Content, Footer, Action
ItemcustomRoot, Content, Title, Description, Actionsvariant (default/outline/muted), size (default/sm)

Form Controls

ComponentBacking LibrarySub-componentsKey Props
ButtoncustomRoot onlyvariant (default/destructive/outline/secondary/ghost/link), size (default/sm/xs/lg/icon); neutral variants hover through --background-modifier-hover
InputcustomRoot onlyref, value (bindable), type
TextareacustomRoot onlyref, value (bindable), field-sizing support
Checkboxbits-uiRoot onlychecked (bindable), indeterminate (bindable)
Switchbits-uiRoot onlychecked (bindable)
Sliderbits-uiRoot onlyvalue (bindable array), orientation; thumb labels render as tooltip-like callouts with bottom arrows on hover/focus
Selectbits-uiRoot, Trigger, Content, Item, Group, GroupHeading, Label, Separator, ScrollUp, ScrollDownsize on Trigger
Togglebits-uiRoot onlypressed (bindable), variant, size; hover uses --background-modifier-hover, pressed uses --background-modifier-active-hover
Toggle Groupbits-uiRoot, Item
Labelbits-uiRoot onlyStandard form label
SearchcustomRoot onlyref, value (bindable), integrated clear button
Color PickercustomRoot only

Overlay Components

ComponentBacking LibrarySub-componentsNotes
Dialogbits-uiRoot, Trigger, Portal, Overlay, Content, Header, Title, Description, Footer, CloseshowCloseButton prop on Content
Dropdown Menubits-ui15 sub-components including Sub, CheckboxItem, RadioItem, RadioGroup
Context Menubits-ui15 sub-components (mirrors Dropdown Menu)
Popoverbits-uiRoot, Trigger, Content, Close
Hover Cardbits-ui LinkPreviewRoot, Trigger, Content
Tooltipbits-uiRoot, Provider, Trigger, Content, Portal
Modalcustom + Dialogopen (bindable), title, contentSupports DocumentFragment content
Date Time Picker Dialogcustom + Dialog/InputRoot onlyModal date/time entry with apply, cancel, and clear actions
Commandbits-uiRoot, Dialog, Input, List, Empty, Group, Item, Separator, Shortcut, LinkItem + Loading, GroupHeadingCommand palette building blocks
AutocompletecustomRoot, List, Item, Empty, FooterPresentational list surfaces (data-slot) aligned with Command/Popover; CodeMirror completion DOM uses @lapis-notes/ui/codemirror-autocomplete.css
CodeMirror lint CSSworkspace-ownedLint range, marker, panel, custom tooltip shell, and inline-problem chrome live in the workspace app stylesheet while consuming UI theme tokens

Trigger-backed overlay wrappers now carry a shared popup-document fallback for their portal target. When callers do not pass explicit portalProps, the shared Dropdown Menu, Context Menu, Hover Card, Tooltip, Popover, and Select wrappers portal into the active trigger element’s ownerDocument.body rather than the ambient root document. That keeps popup-hosted overlays attached to the window that raised them without requiring every popup-aware call site to wire its own portal target.

Data Display

ComponentNotes
TableRoot, Header, Body, Footer, Row, Cell, Head, Caption
Table DnDShared row/column reorder primitives for plugin table previews (@lapis-notes/ui/table-dnd, @lapis-notes/ui/table-dnd/sensors, @lapis-notes/ui/table-dnd/utils): tableReorderSensors, TableDragGrip, dropIndicatorClasses, parseTableDragData, resolveTableDragTargetIndex
Data TableTanStack Table integration via createSvelteTable(options). Helper: FlexRender, renderComponent, renderSnippet, mergeObjects
Badgevariant (default/secondary/destructive/outline), renders as <a> or <span>
TagShared Obsidian-style tag/chip renderer exported from @lapis-notes/ui/tag; used by Markdown previews, Bases, and editable metadata property chips
Progressvalue, max (default 100), animated CSS transform
SkeletonPulsing placeholder animation
SeparatorSimple visual divider
ComponentNotes
BreadcrumbRoot, List, Item, Link, Page, Separator, Ellipsis
Scroll Areabits-ui. orientation (“vertical”/“horizontal”/“both”), customizable scrollbar classes

Feedback

ComponentNotes
Sonner (Toaster)Wraps svelte-sonner. Auto-syncs theme with mode-watcher. Custom CSS variables for theming.
AlertRoot, Title, Description. variant (“default”/“destructive”)

Validation And Exports

The package builds with svelte-package, copies theme.css, styles.css, and codemirror-autocomplete.css into dist, and exposes component subpaths through package exports. Consumers import components directly or through the package index.

During local validation, the package’s Svelte/TypeScript config maps @lapis-notes/ui self-imports and component subpaths back to src/lib so svelte-check runs in a clean checkout without relying on generated dist/ declarations.

The Sidebar is the most complex component group, designed for the workspace shell’s dock panels:

State Management

  • SidebarState class — open, openMobile, state (“expanded”/“collapsed”), isMobile.
  • useSidebar() — Context hook for accessing sidebar state.
  • toggle(), setOpen(), setOpenMobile(), handleShortcutKeydown().
  • Keyboard shortcut: Cmd+B / Ctrl+B.

Constants

  • SIDEBAR_WIDTH = "16rem", SIDEBAR_WIDTH_MOBILE = "18rem", SIDEBAR_WIDTH_ICON = "3rem".
  • SIDEBAR_COOKIE_NAME = "sidebar:state", SIDEBAR_COOKIE_MAX_AGE = 7 days.

Components

  • Provider — Sets up state, CSS variables for widths.
  • Root — Conditionally renders as Sheet on mobile.
  • Content, Header, Footer, Inset — Layout sections.
  • Menu, MenuItem, MenuButton — Menu items with variants (size, variant), tooltip support, icon collapse.
  • MenuSub, MenuSubButton, MenuSubItem — Nested menus.
  • MenuAction, MenuBadge, MenuSkeleton — Auxiliary menu elements.
  • Group, GroupLabel, GroupContent, GroupAction — Section grouping.
  • Input, Rail, Trigger, Separator — Utility components.
  • Rail shares the same hover/focus-visible accent-line treatment as Resizable.Handle: the visible separator line stays centered, grows from 2px to 4px, and switches to var(--interactive-accent) without changing the drag hit target.
  • NestedProvider — For nested sidebar contexts (e.g., right sidebar inside left sidebar).

Dependencies

  • bits-ui — Headless Svelte components.
  • paneforge — Resizable pane library.
  • svelte-sonner — Toast notifications.
  • @tanstack/table-core — Data table engine.
  • mode-watcher — Dark/light theme detection.
  • tailwind-variants / class-variance-authority — Variant styling.
  • lucide-svelte — Icons (SearchIcon, X, ChevronDown, Check, Minus used internally).
  • @dnd-kit/dom, @dnd-kit/svelte — Table row/column reorder sensors and grip helpers (table-dnd); presentation-only, no vault or document model logic.