webui-dev

Installation
SKILL.md
Contains Shell Commands

This skill contains shell command directives (!`command`) that may execute system commands. Review carefully before installing.

WebUI App Development

Use this skill when building or modifying WebUI applications.

Critical rules (memorize these)

  1. The template is the UI. All structure lives in .html. Never document.createElement, innerHTML, insertAdjacentHTML, or appendChild. Show/hide with <if>, repeat with <for>. The only exception is mounting a lazily loaded component.
  2. CSS owns all styling and animation. Never el.style.x =, classList.toggle, or adoptedStyleSheets. Bind ?data-active="{{expr}}" and select [data-active] in CSS. Animate with transition, @keyframes, @starting-style - never element.animate() or a JS animation library.
  3. JavaScript is opt-in. A component needs no .ts file unless it has an @event, a w-ref for an imperative API, a lifecycle hook, a fetch, or a public method API. WebUIElement, @observable, and @attr are optional - add them only when TypeScript reads/writes the value or it is public API. Otherwise the value belongs in the server state JSON.
  4. Use the web platform. <dialog> over a div modal, popover over a JS dropdown, <details> over a JS accordion. Prefer :has(), @container, color-mix(), light-dark(), content-visibility.
  5. Every template binding must exist in the server state JSON. Missing keys render empty, silently.
  6. HTML, CSS, TypeScript are separate files. No JSX. No CSS-in-JS. No JS in templates.
  7. Unwrapped components default to Shadow; --dom light makes them global Light DOM while authored open wrappers stay Shadow. A sole bare top-level <template> explicitly selects Light and is unwrapped even under the Shadow fallback. Light CSS uses ordinary selectors in its owning CSS tree. Use one sole top-level <template shadowrootmode="open"> when the component needs native <slot> projection, Shadow encapsulation, CSS-heavy frequent restyling, root host events, or Shadow-only selectors such as :host. A <slot> and :host fail only in an effective Light component.
  8. Components inside <for> loops do NOT inherit loop variables. Pass data via attributes.
  9. Text bindings are path lookups; comparisons belong in conditions. {{count}} and {{user.name}} resolve a dotted state path - nothing else. {{count > 0}} is looked up as a key literally named count > 0 and renders empty. Comparisons go in <if condition="count > 0"> or ?active="{{section == 'guide'}}". Operators: ==, !=, <, >, <=, >=, &&, ||, !. Forbidden everywhere: ternary (? :), function calls, arithmetic (items.length - 1 resolves as a path and silently fails - send a precomputed lastIndex), mixing && with ||, more than 5 logical operators.
  10. w-ref requires braces. w-ref="{inputEl}", never w-ref="inputEl" - non-braced fails the build with invalid-w-ref. Use it only for imperative APIs (focus, scroll, showModal), never to read state.
  11. @attr({ mode: 'boolean' }) for true/false. Present = true, absent = false. Never use string "false".

Quick reference

Installs
3
Repository
microsoft/webui
GitHub Stars
92
First Seen
Aug 25, 2026
webui-dev — microsoft/webui