UI brands
A brand gives Fuzor Web Components and their parts another tag prefix. A product can write <acme-select> and
<acme-select-item> with the same behavior as <fuzor-select> and <fuzor-select-item>. The canonical tags continue
to work in the same document. CSS and styling drivers still control the presentation.
This page describes the current repository implementation, including the compiler option added after the runtime API. The package version alone does not establish which changes have been published to npm.
Define a brand at runtime
In browser code:
import { defineBrand } from "fuzor/ui/client";
import { registerSelect } from "fuzor/ui/components/select";
defineBrand("acme");
registerSelect();
<acme-select name="plan" value="basic">
<acme-select-trigger>
<acme-select-value placeholder="Choose a plan"></acme-select-value>
</acme-select-trigger>
<acme-select-content>
<acme-select-item value="basic">Basic</acme-select-item>
<acme-select-item value="plus">Plus</acme-select-item>
</acme-select-content>
</acme-select>
In a browser build, defineBrand is also exported by fuzor/ui; fuzor/ui/client selects the browser entry explicitly.
Prefixes contain lowercase letters and digits, starting with a letter, such as acme or zv.
Registration order is flexible:
- A brand aliases components that are already registered and components registered later.
- Existing matching elements in the document upgrade when their alias is defined.
- Defining the same prefix again is harmless, and several prefixes can coexist.
- An alias uses its own subclass because the Custom Elements registry requires a constructor per name.
- A name the page already defined is skipped. Its constructor and behavior stay owned by that page, and the other aliases still register. This holds whether the canonical component or the page-owned name was registered first.
Brands are document-wide. Component registration remains explicit: call individual register… functions, defineAll(),
or use the existing deferred UI runtime.
Let the compiler emit the call
Configure the Vite plugin:
import { defineConfig } from "vite";
import { fuzorPlugin } from "fuzor/vite";
export default defineConfig({
plugins: [fuzorPlugin({ ui: { brand: "acme" } })]
});
The generated browser entry calls defineBrand("acme") before application activation in SPA, MPA and inline builds.
Register the components separately. The option leaves authored HTML unchanged and does not select a driver or bundle
a product stylesheet. A standalone runtime distributed without a Fuzor client entry can call defineBrand directly.
This site's example uses ui: { brand: "demo" }. The ordinary docs controls keep their canonical tags, while the
example below uses <demo-select>. Docs mode supplies deferred registration through the unchanged data-fuzor-ui marker.
Choose a plan, then reset the form to restore Basic. The control is a demo-select.
Contracts that keep their names
| Surface | Under every brand |
|---|---|
| Events | ui:value-change, ui:open-change and their detail shapes |
| Attributes | name, value, disabled, ARIA attributes, data-part, data-fuzor-ui and other existing attributes |
| CSS custom properties | Existing --fuzor-* names, such as --fuzor-dialog-z-index |
| Imports | The existing fuzor/ui/* package entry points |
Only component and part tag names gain aliases. Branded roots find their parts across the registered prefixes, and parts created by a component, such as a select positioner, use their owning root's prefix.
Styles per prefix
Fuzor copies its default structural styles for each registered prefix when styles are injected. This covers layout, hidden parts and overlay structure while keeping custom properties and data attributes unchanged.
Your product's CSS still supplies its own selectors and theme:
acme-select-trigger {
border: 1px solid var(--border);
border-radius: 6px;
padding: 8px 12px;
}
acme-select-positioner { z-index: 1000; }
The brand mechanism does not rewrite an application's stylesheet. Use branded selectors, unchanged part/state attributes or a styling driver for your visual rules. Drivers continue to supply classes and attributes; their corresponding CSS is a separate application dependency.
Form-associated selects
A branded select retains the canonical select's form contract. With ElementInternals, the custom element itself
submits its value under name; a multiple select submits each selected value separately. It keeps no hidden native
select in that path. It supports external form="id" associations, labels, required/custom validation, native form
reset, browser state restoration and disabled fieldsets.
Use setValue([...]) for a multiple selection. Structured form state preserves commas inside individual values.
checkValidity(), reportValidity() and setCustomValidity() use the same validation contract, anchored to the trigger.
DOMs without ElementInternals use the hidden-select path. Use a native <select> when form submission must work
before JavaScript enhancement. See the select reference for the full control API.
Ship a runtime to independently authored pages
A host product can bundle a curated set of components with defineBrand("zv") and distribute that module with its
stylesheet. Page authors then write branded HTML without importing Fuzor or running a build themselves. Avoid registering
tag names already owned by another host library; brand aliases skip pre-owned names.
Zelavis uses this arrangement:
- Its dashboard frontend builds a static
zvruntime and stylesheet. It omits Button, Badge, Card and Stack because the existing service-page element library owns those names. - The Platform injects the runtime and stylesheet into HTML service pages. Its
runtime/service-ui.jsandruntime/service-ui.cssassets are public, CORS-readable static code/styles with no installation data. - Extension pages run in opaque sandboxed frames without
allow-same-origin. Their own files use a signed capability limited to one service bundle and optional Project; API requests use a scoped broker. A page can opt out of injection with<meta name="zelavis-service-ui" content="none">.
Those delivery and permission boundaries belong to the host. Fuzor supplies the component aliases and behavior; branding itself grants no authority to a page.