--- name: obsidian-plugin-ui description: >- Obsidian plugin UI conventions: createEl DOM, data-testid hooks, settings sentence case, CSS variables, command callbacks, and community-review bans for HTML/CSS. Use when building settings tabs, views, codeblock renderers, or /obsidian-plugin-ui. --- # Obsidian plugin UI Canonical rules: [Plugin guidelines](https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines). This skill owns how to paint UI and which DOM/CSS/settings bans to encode as tests. Vault/workspace API bans live in [obsidian-plugin-architecture](../obsidian-plugin-architecture/SKILL.md). ## DOM Do not assign `innerHTML`, `outerHTML`, or `insertAdjacentHTML` on live plugin UI. Paint with Obsidian helpers: ```ts const row = el.createDiv({ cls: "my-row", attr: { "data-testid": "my-row" } }); row.createSpan({ text: label }); el.empty(); // clear before re-render ``` Prefer `createDiv` / `createEl` / `createSpan` over `document.createElement`. If you need a string builder for Node tests that have no DOM, keep it separate from the live paint path. Review looks at shipped `main.js`. A test-only HTML string helper does not save you if the live path still assigns `innerHTML`. Escape any value that still lands in an HTML attribute helper. ## Stable test hooks Every user-visible control Selenium (or a human debugger) will touch gets a `data-testid`. Convention: `--`, e.g. `myplug-timer-start`, `myplug-setting-add`. When you add a hook, add a `assert.match` on the view source in the same change. A renamed hook that only lives in Selenium will pass unit tests and fail in the vault. ## Settings UI - Sentence case everywhere (settings names, commands, buttons). - No top-level heading that is just the plugin name. No "settings" in section headings. - Prefer `new Setting(el).setName("…").setHeading()` over raw `

`. - Obsidian 1.13+ settings search reads `getSettingDefinitions()`. Keep `display()` as the pre-1.13 fallback. Do not recurse into `display()` to redraw; call `update()` when it exists. - Do not call APIs newer than `minAppVersion` without a feature detect. Destructive chrome can use CSS class `mod-warning` when `setDestructive` is too new for your floor. ## CSS Use Obsidian CSS variables (`--text-normal`, `--background-modifier-border`, …). Do not hardcode `element.style.color` for theme-facing chrome. Community directory review has rejected these in shipped `styles.css`: | Ban | Replacement | | --- | --- | | `:has(` | Host class or JS that walks ancestors | | `!important` | Raise specificity (`button.my-class`, `img.my-cover`) | | `scrollbar-width` | `::-webkit-scrollbar` / `-ms-overflow-style` if you must style scrollbars | Do not use lookbehind regexes if `isDesktopOnly` is false. ## Commands - No default hotkeys. - `callback` for unconditional commands; `checkCallback` / `editorCheckCallback` when a condition or editor is required. - UI copy is sentence case. ## Notices and logging - User-facing feedback: `Notice` - No `console.log` in shipped code - Register events with `this.registerEvent` so disable/unload drops them ## Encode as tests [references/guidelines-checklist.md](references/guidelines-checklist.md) is the assertion list. Pattern: `tests/*selectors*.mjs` with `assert.match` / `assert.doesNotMatch` on source files.