Skip to main content

obsidian-css

Style Obsidian plugin UI with Tailwind + native components. Use when writing or reviewing any styled React/JSX in apps/obsidian/ — picking colors, spacing, typography, borders, radius, shadows, dark-mode tokens, or choosing between Tailwind utilities, Obsidian native components, and custom CSS. Also use for .zt-root preflight setup, zt: prefix usage, theme compatibility, component wrapper selection, or any CSS/styling decision in the plugin.

소스 정보

저장소
aidenlx/zotlit
최근 소스 활동
2026년 8월 19일 17:31
감지된 SKILL.md 언어
영어
스타
1,029
포크
64

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
8 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
obsidian-css
description
Style Obsidian plugin UI with Tailwind + native components. Use when writing or reviewing any styled React/JSX in apps/obsidian/ — picking colors, spacing, typography, borders, radius, shadows, dark-mode tokens, or choosing between Tailwind utilities, Obsidian native components, and custom CSS. Also use for .zt-root preflight setup, zt: prefix usage, theme compatibility, component wrapper selection, or any CSS/styling decision in the plugin.
# Obsidian Plugin CSS & Tailwind Style Guide Plugin UI should feel like part of Obsidian — same colors, same spacing, same dark/light handling — without the plugin needing to know which theme the user has installed. Theme authors do this by **overriding** Obsidian's built-in CSS variables; plugin authors do it by **consuming** them through Tailwind utility classes. The biggest mistake is hardcoding values (`#1e1e1e`, `12px`, `1px solid #ccc`). Hardcoded values look fine in the default theme and break in every other theme. Always use Tailwind tokens, which are backed by Obsidian CSS variables. ## Styling approach **Tailwind-first with `zt:` prefix.** Default to Tailwind utility classes for all styling. Avoid writing raw CSS stylesheets. The Tailwind theme in `src/zt-main.css` maps Obsidian CSS variables to Tailwind tokens. **Every utility class uses the `zt:` prefix** — `zt:flex`, `zt:gap-2`, `zt:bg-background`, `zt:text-muted-foreground`, `zt:rounded-md`. Variants chain after the prefix: `zt:hover:opacity-100`, `zt:@md:columns-2`. This is Tailwind v4's `prefix()` feature applied to both `theme.css` and `utilities.css` imports — it scopes compiled selectors so they never collide with other plugins' Tailwind output. Custom `@theme` variables defined in `zt-main.css` (colors, radii, shadows mapped to Obsidian tokens) keep their authored names (no prefix). When no Tailwind token exists for an Obsidian variable, either extend `zt-main.css` (follow the existing pattern) or use Tailwind's arbitrary CSS variable syntax: `zt:bg-(--obsidian-var)`, `zt:text-(--some-color)`, `zt:p-(--size-4-3)`, etc. These compile to `var(--…)` at build time with full utility support. Use `cn()` from `@/lib/utils` to merge Tailwind classes with conflict resolution — never raw string concatenation. `cn()` is prefix-aware via `twMerge` from `@/lib/tw`. For React components with Obsidian modifier classes (`mod-cta`, `is-enabled`, `clickable-icon`), use `tv` from `@/lib/tw` (not directly from `tailwind-variants`) for variant composition — it's pre-configured with the `zt` prefix for correct class merging. See the existing wrappers in `src/components/obsidian/` for the pattern. ## The three rules 1. **Use Tailwind tokens, never hardcode.** Hardcoded `#hex`, `rgb(…)`, and `8px` values look correct in the default theme and break in every other theme. Use mapped tokens (`zt:bg-background`, `zt:text-muted`, `zt:rounded-md`). For spacing, Tailwind's default scale (`zt:gap-2`, `zt:p-3`) is fine — Obsidian's `--size-4-N` variables are just fixed `4px` multiples that no theme overrides. See `references/foundations.md` for available variables. 2. **Prefer native components.** Obsidian fully styles `<button>`, `<input>`, `<select>`, `<textarea>`, and toggle — rebuilding these from Tailwind primitives fights Obsidian's styles and breaks across themes. Use bare elements or the React wrappers from `src/components/obsidian/`. 3. **No `!important`, scope custom CSS.** `!important` blocks user CSS snippets — they can never override your styles. Scope custom CSS under `zt-`-prefixed classes and keep specificity low. ## Quick decision tree Picking a style goes: 1. **A native component exists** → use the React wrapper from `src/components/obsidian/` (or the imperative `obsidian` API class). Don't restyle it with Tailwind — Obsidian's preflight handles appearance. 2. **Color** → pick a semantic token (`zt:text-muted`, `zt:bg-background`, `zt:text-accent-foreground`), not a raw palette one. Semantic tokens already track light/dark and the user's accent color. See `references/foundations.md`. 3. **Spacing / size** → use Tailwind's default spacing scale (`zt:gap-2`, `zt:p-3`, `zt:mt-4`). Obsidian's `--size-4-N` variables are just fixed multiples of 4px (never overridden by themes) and map 1:1 to Tailwind's scale, so there's no reason to use them directly. 4. **Radius** → `zt:rounded-sm/md/lg/xl` (mapped to `--radius-s/m/l/xl`). 5. **Typography** → `zt:text-xs/sm/base/lg` (mapped to Obsidian UI font sizes). See `references/foundations.md#typography`. 6. **A component CSS variable exists** (e.g. `--modal-background`, `--button-radius`, `--tab-text-color`) → use the arbitrary variable syntax (`zt:bg-(--modal-background)`) or extend `zt-main.css`. See `references/components.md`, `references/editor.md`, `references/window.md`, `references/plugins.md`. 7. **Nothing fits** → use the arbitrary variable syntax (`zt:bg-(--obsidian-var)`) to reference the Obsidian variable directly. If you want to expose it for user snippets, add a local custom property at your component root (`.zt-foo { --zt-foo-bg: var(--background-secondary); }`) and consume it via `zt:bg-(--zt-foo-bg)`. ## Obsidian element preflights Obsidian fully styles `<button>`, `<input>` (all types), `<textarea>`, and `<select>` via its global stylesheet — see `references/preflights.md` for the full table. **Lean into this — don't fight it with Tailwind resets.** ### What this means for you - **Don't add Tailwind background/border/padding/radius classes to these elements** — you'll be fighting Obsidian's styles. Use the elements bare and they look correct. - **Tailwind layout classes are safe** — `zt:flex`, `zt:gap-2`, `zt:w-full`, `zt:mt-2` etc. don't conflict because Obsidian's preflight doesn't set layout on these elements. - **Prefer native components** — bare `<input type="checkbox">`, `<input type="radio">`, and other preflighted elements need no wrapper. See next section for which elements have React wrappers and which don't. - **The flip side** — non-form semantic elements (`blockquote`, `p`, headings, lists, `hr`, …) that Obsidian doesn't reset get cleaned up by a **scoped preflight** applied to plugin roots. See **Scoped preflight (`.zt-root`)** below. ## Scoped preflight (`.zt-root`) `src/zt-main.css` applies Tailwind's preflight **scoped to plugin UI roots**, never globally: ```css @layer theme, base, components, utilities; /* explicit order: utilities > base */ @import "tailwindcss/theme.css" layer(theme) prefix(zt); /* …@theme tokens… */ @import "tailwindcss/utilities.css" layer(utilities) prefix(zt); @layer base { .zt-root { @import "tailwindcss/preflight.css"; } /* Tailwind inlines + scopes this at build */ } ``` **Add `zt-root` to the container the view owns** — `ItemView.contentEl`, a modal's `contentEl`, a settings pane — not inside the React tree. Then write normal **semantic HTML** — `<blockquote>`, `<p>`, `<ul>`/`<li>`, `<h2>`, `<hr>` — plus Tailwind border utilities; they render clean because preflight zeroes the UA margins/padding and sets `border-style: solid`. No `div role` workaround, no `border-solid`. ```tsx // Set the scope on the container the view owns — not in the React tree: class AnnotationView extends ItemView { async onOpen() { this.contentEl.classList.add("zt-root"); createRoot(this.contentEl).render(<AnnotView />); } } // Inside, semantic HTML + border utilities just work — no div+role, no border-solid: <blockquote className="zt:border-l-2 zt:pl-2" style={{ borderLeftColor: color }}>{text}</blockquote> <p className="zt:select-text">{text}</p> ``` ### Why it's safe Preflight sits in `@layer base` — your `zt:` utilities (`@layer utilities`) outrank it, and Obsidian's unlayered stylesheet outranks *all* layers. So preflight only overrides **browser UA defaults** (the leaks you want gone: `<blockquote>` `margin:1em 40px`, `<p>` `margin:1em 0`, `<ul>`/`<ol>` 40px indent, etc.), while your spacing/border utilities and Obsidian's native control styles both work normally inside `.zt-root`. ### Caveats - **It does NOT reset Obsidian's own *unlayered* bare-element rules** (unlayered beats `@layer base`), and the bare-rule set is bigger than it looks. **Structural:** `<hr>` (`border-top: var(--hr-thickness)` + `margin: 2rem 0` — note `.markdown-rendered hr` only re-declares the border, so the 2rem margin survives even there); `<h1>`–`<h6>` (per-level themed typography — size/weight/color/font/line-height — + `var(--p-spacing)` margin-block); `<ol>` (`list-style-type: var(--list-numbered-style)` → **numbering survives**); nested `ul` selectors like `ul ul, ol ul` (`list-style-type: disc`) — but a **top-level** bare `<ul>` has no rule at all, so preflight strips its markers; `ul > li, ol > li` (`text-align: start`) and their `::marker` color. **Inline elements:** `a`/`a:hover` (accent color, underline, link weight/cursor — this beats preflight's `a { color: inherit }`, so links stay Obsidian-styled inside `.zt-root`), `b`/`strong` (bold weight + color), `i`/`em` (italic + color), `kbd` (fully styled: mono font, background, padding, radius). **Other globals:** `button` (full Obsidian button box, distinct from the `<button>` preflight discussion above), `iframe`, `audio`, `*` (`box-sizing: border-box`), and `:focus` (`outline: none`). Everything else (`<p>`, `<blockquote>`, `<code>`, `<pre>`, `<table>`/`<th>`/`<td>`, `<img>`, `<video>`, `<mark>`, `<sub>`/`<sup>`, `<fieldset>`, a **top-level** `<ul>`, …) has *no* bare Obsidian rule — pure Chrome UA, so preflight fully governs them. Neutralize the ones you don't want with a utility/inline style or a plain `<div>`. - **`.zt-root` is for your own custom DOM.** Markdown rendered via `MarkdownRenderer` inside it still looks right (Obsidian's `.markdown-rendered` rules are unlayered → they win), but don't lean on preflight to style markdown. ### Borders Inside `.zt-root` a width utility is all you need: `zt:border` / `zt:border-l-2` render **solid** (preflight supplies the style) and `zt:divide-y zt:divide-border` gives clean section rules — no `zt:border-solid` / `zt:divide-solid`. For a data-driven color (e.g. an annotation highlight) keep the width in the utility and set `style={{ borderLeftColor: color }}`. ### No `.zt-root`? (fallback) Outside a scoped root there is no preflight, so a width utility sets only width — `border-style` stays `none` (so `zt:border` alone is invisible) and unset sides fall back to UA `medium`, making `zt:border-l-2 zt:border-solid` a full **box**. There, add `zt:border-solid` / set the border inline, or render a `<div>` with the matching ARIA role (`role="blockquote"`, `role="paragraph"`, `role="list"` + child `role="listitem"`, `role="heading"` + `aria-level`, `role="separator"`). Prefer adding `zt-root` — simpler, and keeps real semantics. ## Native components Prefer Obsidian's native components over building custom equivalents from Tailwind primitives. They get automatic theme compatibility, accessibility, and consistent behavior across Obsidian versions. **In React**, use the wrappers in `src/components/obsidian/` for components that need modifier-class logic or non-trivial behavior: - `Button`, `IconButton`, `Toggle`, `Dropdown` (+ `DropdownItem`, `DropdownGroup`), `Slider`, `Color`, `Icon`, `SearchInput` **`<input type="text/search/email/password/number/date/datetime-local">`, `<input type="checkbox">`, `<input type="radio">` and `<textarea>` need no wrapper.** Obsidian's preflight fully styles these elements — use them directly in JSX. Don't create thin React wrappers around them; the native elements already look correct. - Use `AutosizeTextarea` from `react-textarea-autosize` when the textarea should expand with content (e.g. template editors, note fields). ```tsx // Correct — bare elements, Obsidian styles them <input type="text" value={text} onChange={(e) => setText(e.currentTarget.value)} /> <textarea value={body} onChange={(e) => setBody(e.currentTarget.value)} rows={4} /> <input type="checkbox" checked={checked} onChange={(e) => setChecked(e.currentTarget.checked)} /> <input type="radio" name="group" value="a" checked={val === "a"} onChange={() => setVal("a")} /> // For auto-growing textareas, use the wrapper <AutosizeTextarea value={body} onChange={setBody} minRows={2} maxRows={8} /> ``` **In imperative DOM** (settings tabs, modals built with the Obsidian API), use the `obsidian` module classes directly: - `ButtonComponent`, `ToggleComponent`, `TextComponent`, `TextAreaComponent`, `DropdownComponent`, `ColorComponent`, `SliderComponent` Read the wrapper source files for the full API. Each wrapper's JSDoc links to `tooltipAttrs` for tooltip usage. ## Tooltips Obsidian renders `aria-label` as a hover tooltip via global `pointerover` delegation. No extra library needed. **React** — use `tooltipAttrs()` from `@/lib/utils` and spread onto the element: ```tsx import { tooltipAttrs } from "@/lib/utils"; <Button variant="cta" {...tooltipAttrs("Saves the current file")}> Save </Button> <IconButton icon="settings" {...tooltipAttrs("Settings", { placement: "top" })} /> ``` **Imperative DOM** — use `setTooltip` from the `obsidian` module:
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기