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/webui

Or install via Cargo for standalone CLI use:

cargo install microsoft-webui-cli

Commands #

#

Build a WebUI application from an app folder.

webui build [APP] --out <OUT> [--entry <FILE>] [--css <MODE>] [--plugin <NAME>] [--components <SOURCE>]...

Arguments:

ArgumentDescriptionDefault
APPPath to the app folder. (current directory)
--out <OUT>Output folder for protocol and assets(required)
--entry <FILE>Entry HTML file nameindex.html
--css <STRATEGY>CSS delivery strategy: link, style, or modulelink
--plugin <NAME>Load a parser plugin (e.g., fast-v3)(none)
--dom <STRATEGY>DOM strategy: shadow or lightshadow
--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:

ModeBehavior
linkEmits <link> tags referencing external .css files. CSS files are copied to the output folder.
styleEmbeds CSS content directly in <style> tags inside shadow DOM templates. No separate CSS files are written.
moduleEmits <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:

StrategyBehavior
shadowComponents render inside <template shadowrootmode="open">. Style encapsulation via Shadow DOM. Default.
lightComponents 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 @microsoft/fast-element 3.x plugin (hydration support)
webui build ./my-app --out ./dist --plugin=fast-v3

# 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/components

#

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:

ArgumentDescription
FILEPath 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'

#

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:

ArgumentDescriptionDefault
APPPath to the template/component directory. (current directory)
--state <FILE>Path to JSON state file for rendering(required)
--servedir <DIR>Directory served at /*(optional)
--watchEnable file watching + HMRfalse
--port <PORT>Port to bind the development server3000
--entry <FILE>Entry HTML file nameindex.html
--css <MODE>CSS delivery strategy: link, style, or modulelink
--plugin <NAME>Load parser + handler plugins (e.g., fast-v3)(none)
--dom <STRATEGY>DOM strategy: shadow or lightshadow
--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:

  1. Builds the protocol from your APP directory (no separate webui build step needed)
  2. Renders the entry template with state data
  3. Serves the rendered HTML with an injected live-reload script
  4. If --watch is enabled, watches app, state, and asset files for changes
  5. If --watch is enabled, automatically rebuilds and re-renders when files change
  6. If --watch is 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 @microsoft/fast-element 3.x plugin for hydration
webui serve ./my-app --state ./state.json --plugin=fast-v3 --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 --watch

Routes:

PathDescription
/ or /index.htmlRendered HTML with live-reload script
/*Static files from --servedir (when provided)
/hmrHMR 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 scripts

Component 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 exists

Plugins #

The --plugin flag loads framework-specific extensions that customize both parsing and rendering behavior.

Available Plugins #

PluginDescription
webuiWebUI Framework compiled templates and hydration markers.
fast-v3@microsoft/fast-element 3.x hydration support for new FAST apps. Parser skips runtime attrs, emits binding data, and injects <f-template> wrappers. Handler injects <!--fe:b-->, <!--fe:/b-->, <!--fe:r-->, <!--fe:/r-->, and data-fe="COUNT" markers.
fast-v2Deprecated @microsoft/fast-element 2.x compatibility. Emits legacy <!--fe-b$$...-->, <!--fe-repeat$$...-->, data-fe-b-INDEX, and data-fe-c-INDEX-COUNT markers.
fastDeprecated compatibility alias for fast-v2. Use fast-v3 for @microsoft/fast-element 3.x migrations.

No plugin is enabled by default. Select fast-v3 explicitly for @microsoft/fast-element 3.x apps; fast remains accepted only to avoid silently changing existing @microsoft/fast-element 2.x output.

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/button

npm package requirements:

The package's package.json must have:

FieldPurpose
exports["./template-webui.html"]Path to the component's HTML template
exports["./styles.css"]Path to the component's CSS (optional)
customElementsPath 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-kit

Multiple Sources #

Combine multiple --components flags:

webui build ./my-app --out ./dist \
  --components @reactive-ui \
  --components ./shared/components \
  --components my-widget

Caching #

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 #