CLI Reference
The webui command-line tool is the primary way to build WebUI applications. It takes your app folder containing HTML templates and web components, and produces the WebUI protocol output ready for server-side rendering.
Installation
Install via npm:
npm install @microsoft/webuiOr install via Cargo for standalone CLI use:
cargo install microsoft-webui-cliCommands
webui build
Build a WebUI application from an app folder.
webui build [APP] --out <OUT> [--entry <FILE>] [--css <MODE>] [--plugin <NAME>] [--components <SOURCE>]...Arguments:
| Argument | Description | Default |
|---|---|---|
APP | Path to the app folder | . (current directory) |
--out <OUT> | Output folder for protocol and assets | (required) |
--entry <FILE> | Entry HTML file name | index.html |
--css <STRATEGY> | CSS delivery strategy: link, style, or module | link |
--plugin <NAME> | Load a parser plugin (e.g., fast) | (none) |
--dom <STRATEGY> | DOM strategy: shadow or light | shadow |
--components <SOURCE> | Additional component sources (npm packages or local paths). Repeatable. | (none) |
Path inputs for APP, --state, and --servedir support absolute paths, relative paths, ~/..., and file://... URI-style values.
CSS Modes:
| Mode | Behavior |
|---|---|
link | Emits <link> tags referencing external .css files. CSS files are copied to the output folder. |
style | Embeds CSS content directly in <style> tags inside shadow DOM templates. No separate CSS files are written. |
module | Emits <style type="module" specifier="component"> definitions and adds shadowrootadoptedstylesheets to <template> tags. The browser shares a single CSSStyleSheet across all shadow roots that adopt it. No separate CSS files are written. Based on the Declarative CSS Module Scripts proposal. |
DOM Strategies:
| Strategy | Behavior |
|---|---|
shadow | Components render inside <template shadowrootmode="open">. Style encapsulation via Shadow DOM. Default. |
light | Components render as direct children. No shadow boundary. 26% faster FCP on high-component-count pages. |
See Performance - Light DOM vs Shadow DOM for benchmarks and guidance.
Examples:
# Build from current directory
webui build --out ./dist
# Build a specific app folder
webui build ./my-app --out ./dist
# Use a custom entry file
webui build ./my-app --out ./dist --entry home.html
# Build with style CSS (no external CSS files)
webui build ./my-app --out ./dist --css style
# Build with the FAST plugin (hydration support)
webui build ./my-app --out ./dist --plugin=fast
# Build with external component packages
webui build ./my-app --out ./dist --components @reactive-ui
# Build with components from a local shared library
webui build ./my-app --out ./dist --components ./shared/componentswebui inspect
Inspect a protocol.bin file by converting it to JSON and printing to stdout. Useful for debugging and piping to tools like jq.
webui inspect <FILE>Arguments:
| Argument | Description |
|---|---|
FILE | Path to a protocol.bin file |
Examples:
# Inspect a protocol file
webui inspect dist/protocol.bin
# Pretty-print a specific fragment with jq
webui inspect dist/protocol.bin | jq '.fragments["index.html"]'
# Count total fragments
webui inspect dist/protocol.bin | jq '.fragments | keys | length'webui serve
Start a development server that builds, renders, and serves a WebUI application. Enable live reload with --watch.
webui serve [APP] --state <FILE> [--servedir <DIR>] [--watch] [--port <PORT>] [--entry <FILE>] [--css <MODE>] [--dom <MODE>] [--plugin <NAME>] [--components <SOURCE>]... [--api-port <PORT>] [--theme <VALUE>]Arguments:
| Argument | Description | Default |
|---|---|---|
APP | Path to the template/component directory | . (current directory) |
--state <FILE> | Path to JSON state file for rendering | (required) |
--servedir <DIR> | Directory served at /* | (optional) |
--watch | Enable file watching + HMR | false |
--port <PORT> | Port to bind the development server | 3000 |
--entry <FILE> | Entry HTML file name | index.html |
--css <MODE> | CSS delivery strategy: link, style, or module | link |
--plugin <NAME> | Load parser + handler plugins (e.g., fast) | (none) |
--dom <STRATEGY> | DOM strategy: shadow or light | shadow |
--components <SOURCE> | Additional component sources (npm packages or local paths). Repeatable. | (none) |
--api-port <PORT> | Proxy route requests to your API server on this port. The dev server forwards navigation requests so your backend can provide real state data. | (none) |
--theme <VALUE> | Design token theme: a path to a JSON file or an npm package name. Resolved tokens are injected into the render state. | (none) |
The APP directory should contain your entry HTML and component files.
What it does:
- Builds the protocol from your
APPdirectory (no separatewebui buildstep needed) - Renders the entry template with state data
- Serves the rendered HTML with an injected live-reload script
- If
--watchis enabled, watches app, state, and asset files for changes - If
--watchis enabled, automatically rebuilds and re-renders when files change - If
--watchis enabled, connected browsers reload automatically via the polling HMR backend
Examples:
# Start serving the current directory
webui serve . --state ./state.json --servedir ./assets
# Start serving a specific templates directory
webui serve ./examples/app/hello-world/templates --state ./examples/app/hello-world/data/state.json --servedir ./examples/app/hello-world/assets --watch
# Use a custom port
webui serve ./my-app --state ./state.json --servedir ./assets --port 9090 --watch
# Use style CSS mode
webui serve ./my-app --state ./state.json --servedir ./assets --css style --watch
# Use the FAST plugin for hydration
webui serve ./my-app --state ./state.json --plugin=fast --port 3001
# Dev server with external components (--watch watches local paths)
webui serve ./my-app --state ./state.json --components @reactive-ui --watch
# Proxy route requests to your API server (e.g. Express on port 4000)
webui serve ./my-app --state ./state.json --api-port 4000 --watch
# Apply a design token theme from an npm package
webui serve ./my-app --state ./state.json --theme @my-org/brand-tokens --watch
# Apply a design token theme from a local JSON file
webui serve ./my-app --state ./state.json --theme ./themes/dark.json --watchRoutes:
| Path | Description |
|---|---|
/ or /index.html | Rendered HTML with live-reload script |
/* | Static files from --servedir (when provided) |
/hmr | HMR version endpoint (polling backend, only when --watch) |
App Folder Structure
The CLI expects your app folder to contain an entry HTML file and optionally web component files:
my-app/
├── index.html # Entry template (or specify with --entry)
├── my-card.html # Web component: <my-card>
├── my-card.css # Component styles (auto-discovered)
├── nav-bar.html # Web component: <nav-bar>
├── nav-bar.css # Component styles
├── styles.css # Global styles
└── app.js # Client-side scriptsComponent Discovery
The CLI automatically discovers web components in your app folder:
- HTML files with a hyphen in the name are treated as components (e.g.,
my-card.html→<my-card>) - CSS files with the same name are automatically paired (e.g.,
my-card.css) - Components are registered and available for use in your templates
- Discovery is recursive - components in subdirectories are also found
Entry Template
Your entry HTML file is a standard HTML document using WebUI directives:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My App</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>Hello, {{name}}!</h1>
<for each="item in items">
<my-card>{{item.title}}</my-card>
</for>
<if condition="showFooter">
<footer>Thanks for visiting</footer>
</if>
</body>
</html>Build Output
The --out folder will contain:
dist/
├── protocol.bin # The WebUI protocol (protobuf binary)
├── my-card.css # Component CSS (--css link only)
└── nav-bar.css # Component CSS (--css link only)With --css style, only protocol.bin is written - CSS is embedded directly in the protocol's template fragments.
protocol.bin
The protocol file contains a serialized WebUIProtocol structure (protobuf binary) with all parsed fragments. This file is consumed by a platform handler at runtime to render HTML with your application state.
The binary format is not human-readable. The equivalent proto schema structure looks like:
// WebUIProtocol
fragments {
key: "index.html"
value: FragmentList {
fragments: [
Raw { value: "<h1>Hello, " },
Signal { value: "name", raw: false },
Raw { value: "!</h1>" },
For { item: "item", collection: "items", fragment_id: "for-1" }
]
}
key: "for-1"
value: FragmentList {
fragments: [
Component { fragment_id: "my-card" },
Signal { value: "item.title", raw: false }
]
}
}Error Messages
The CLI provides helpful error messages with suggestions:
✘ Failed to read /path/to/app/index.html
caused by: No such file or directory (os error 2)
hint: Try using --entry <file> to specify a different entry file ✘ App folder not found: /nonexistent/path
caused by: No such file or directory (os error 2)
hint: Check that the app folder path existsPlugins
The --plugin flag loads framework-specific extensions that customize both parsing and rendering behavior.
Available Plugins
| Plugin | Description |
|---|---|
fast | FAST-HTML hydration support. Parser skips runtime attrs, emits binding data, injects <f-template> wrappers. Handler injects hydration comment markers. |
See Plugins for detailed documentation.
External Component Sources
The --components flag lets you discover components from npm packages or local directories outside your app folder. This is useful for shared component libraries.
npm Packages
Pass an npm package name. The package must already be installed in node_modules/.
# Single package
webui build ./my-app --out ./dist --components my-widget
# Scoped package (discovers all sub-packages)
webui build ./my-app --out ./dist --components @reactive-ui
# Specific scoped sub-package
webui build ./my-app --out ./dist --components @reactive-ui/buttonnpm package requirements:
The package's package.json must have:
| Field | Purpose |
|---|---|
exports["./template-webui.html"] | Path to the component's HTML template |
exports["./styles.css"] | Path to the component's CSS (optional) |
customElements | Path to a Custom Elements Manifest JSON file |
The Custom Elements Manifest provides the component tag name via modules[].declarations[].tagName.
Resolution: The CLI searches for node_modules/ by walking up from the app directory, matching Node.js module resolution behavior. Symlinks (pnpm, npm workspaces) are resolved automatically.
Local Paths
Pass a filesystem path to discover components the same way the app directory is scanned.
# Relative path
webui build ./my-app --out ./dist --components ./shared/components
# Absolute path
webui build ./my-app --out ./dist --components /libs/ui-kitMultiple Sources
Combine multiple --components flags:
webui build ./my-app --out ./dist \
--components @reactive-ui \
--components ./shared/components \
--components my-widgetCaching
Discovered npm package components are cached at ~/.webui/cache/components/ to avoid re-traversing on every build. The cache is automatically invalidated when a package's package.json changes. Local path sources are always re-scanned.
Next Steps
- Hello World Tutorial - Build your first WebUI app
- Components - Learn about web components
- Template Directives -
<for>,<if>, and{{}} - Platform Handlers - Render protocols with state at runtime