Documentation · Web Components

43 custom elements that use your stylesheet instead of their own.

The mue-* elements are plain ES modules with no shadow roots. They add markup and behaviour inside your document, so styles/components.css, the design tokens and the html.dark flip reach them without ::part or adoptedStyleSheets plumbing. One <script type="module"> registers every tag. Nothing is hosted, nothing is published to npm, and nothing needs a build.

43tags registered
40element modules
11mue-* events
0runtime dependencies

Load them four ways

Pick the one that matches the project you already have. The first needs no tooling at all, which is the point of this port.

a page that renders
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  --> <link rel=stylesheet href="design-tokens/engine.css">
  <link rel=stylesheet href="styles/components.css">
  <script src="styles/mue.js" defer></script>
  --> <script type="module" src="frameworks/web-components/index.js"></script>
</head>
<body class="page">
  <mue-button variant="primary">Save changes</mue-button>
  <mue-input label="Email" type="email" name="email"
            helper="Never shared with third parties."></mue-input>
  <mue-card variant="elevated">
    <div class="card-body">…</div>
  </mue-card>
</body>
</html>
mue add --framework web-components
$ node tools/mue.js add btn-buttons --framework web-components --into ../demo-app --create
added 1 web-components binding(s) into ../demo-app
btn-buttons — Buttons (stable)
  frameworks/web-components/components/mue-button.js  [written]
  install: <script type="module" src="frameworks/web-components/index.js"></script>
  import:  import "./frameworks/web-components/index.js" (registers every mue-* tag)
  tag:     <mue-button>
  verified: … (full line printed; quoted on the agent guide)

package files: 3 — 15 shared layer file(s) (15 written)

NOTES
  · web-components/index.js was written with 39 module reference(s) left out —
    those components are not in this install.

The file: install is the weakest of the four: it registers mue-web-components as a link to the folder inside the checkout rather than a copy, so deleting or moving the repository breaks the import. It is also not a published package — npm i mue-web-components will fail, because nothing has been published. The CLI page explains why this project installs files instead of resolving a registry.

What an element actually renders

Dumped from the browser after the modules registered, not written by hand. This is the contract: the host builds real child nodes out of the classes in styles/components.css, and the theme sheet sees them because they are in the document.

outerHTML, three elements
<mue-button variant="primary"><button type="button" class="btn-primary">Save changes</button></mue-button>

<mue-input label="Email" type="email" name="email" helper="…"><div class="field">
  <label class="field-label" for="mue-input-1">Email</label>
  <input id="mue-input-1" type="email" placeholder="" name="email">
  <p class="field-helper" id="mue-input-1-helper">Never shared with third parties.</p>
</div></mue-input>

<mue-card variant="elevated" class="card card-elevated">…</mue-card>
No shadow rootEvery host in the suite reports shadowRoot === null. That is a design decision, not a gap — frameworks/web-components/README.md documents it under “Why light DOM”.
The host is the componentmue-card gains class="card card-elevated" on itself rather than wrapping your children in a div, so your own classes sit beside the ones it adds.
Real form controlsmue-button renders a <button type="button">; mue-input renders an <input> whose id is bound to a <label for>, so keyboard focus, form submission and a screen reader’s accessible name all come from HTML rather than from ARIA emulation.
The costBecause the host rebuilds its children, the child markup per element is fixed and documented. You cannot nest arbitrary content inside mue-button and expect it to survive — the gallery shows what each element accepts.

Element reference

All 43 registered tags, read out of the 40 modules in frameworks/web-components/components/. Two modules ship more than one tag: mue-sidebar.js registers three and mue-guidance.js registers two. “slot children” means the element expects you to write its inner markup; the classes it looks for are in the gallery.

TagAttributes read by the moduleEmitsModuleCatalog entry
<mue-accordion>— slot children—mue-accordion.jsui-sections
<mue-accordion-item>open, labelmue-changemue-accordion-item.jsno catalog entry
<mue-avatar>src, alt, initials, name—mue-avatar.jsno catalog entry
<mue-avatar-group>label—mue-avatar-group.jsno catalog entry
<mue-badge>variant, solid, dot—mue-badge.jsui-descriptions
<mue-breadcrumbs>items, separator, label—mue-breadcrumbs.jsnav-breadcrumbs
<mue-button>variant, size, state, type, disabled, block, icon, aria-label—mue-button.jsbtn-buttons
<mue-card>variant, interactive, selected—mue-card.jsui-cards
<mue-charts>type, title, desc, values, labels, colors, legend, summary—mue-charts.jsui-charts
<mue-checkbox>label, name, value, checked, disabledmue-changemue-checkbox.jsfrm-checkboxes
<mue-datatable>— slot childrenmue-select, mue-expand, mue-sort, mue-filtermue-datatable.jsui-datatable
<mue-date-picker>label, type, name, value, min, max, step, placeholder, helper, message, state, disabled, requiredmue-changemue-date-picker.jsfrm-date-picker
<mue-dialog>open, titlemue-open, mue-closemue-dialog.jsui-dialogs
<mue-dropdown>openmue-select, mue-open, mue-closemue-dropdown.jsui-dropdowns
<mue-dropzone>— slot childrenmue-remove, mue-changemue-dropzone.jsfrm-dropzone
<mue-empty>icon, title, desc, label—mue-empty.jsfb-empty
<mue-error>title, desc, inline, label—mue-error.jsfb-error
<mue-fab>static, revealmue-close, mue-stepmue-guidance.jsui-guidance
<mue-footer>brand, desc, copyright, columns, social, label—mue-footer.jsui-footers
<mue-guidance>currentmue-close, mue-stepmue-guidance.jsui-guidance
<mue-header-nav>brandmue-navigatemue-header-nav.jsnav-header
<mue-input>label, type, name, value, placeholder, helper, message, state, disabled, requiredmue-changemue-input.jsfrm-input-fields
<mue-modals>open, title, widthmue-close, mue-openmue-modals.jsui-modals
<mue-nav-menu>labelmue-select, mue-navigatemue-nav-menu.jsnav-menu
<mue-notification>type, variant, title, message, openmue-closemue-notification.jsui-notifications
<mue-pagination>total, page, window, labelmue-page-changemue-pagination.jsnav-pagination
<mue-popover>trigger, placement, icon, openmue-close, mue-openmue-popover.jsui-popover
<mue-progress>label, value, min, max, variant, striped, indeterminatemue-changemue-progress.jsfb-progress
<mue-radio>label, name, value, checked, disabledmue-changemue-radio.jsfrm-radio-buttons
<mue-radio-group>label, name, value, disabledmue-changemue-radio-group.jsno catalog entry
<mue-rating>value, max, caption, label, interactive, namemue-changemue-rating.jsui-rating
<mue-select>label, name, value, helper, message, state, disabled, requiredmue-changemue-select.jsfrm-select-menus
<mue-sidebar>— slot childrenmue-navigatemue-sidebar.jsnav-sidebar
<mue-sidebar-heading>— slot childrenmue-navigatemue-sidebar.jsnav-sidebar
<mue-sidebar-link>— slot childrenmue-navigatemue-sidebar.jsnav-sidebar
<mue-skeleton>variant, width—mue-skeleton.jsfb-skeleton
<mue-stepper>currentmue-stepmue-stepper.jsfb-stepper
<mue-switch>label, name, value, checked, disabledmue-changemue-switch.jsfrm-switches
<mue-tab>— slot children—mue-tab.jsno catalog entry
<mue-table>striped, compactmue-sortmue-table.jsui-tables
<mue-tabpanel>— slot children—mue-tabpanel.jsno catalog entry
<mue-tabs>selected, labelmue-changemue-tabs.jsnav-tabs
<mue-textarea>label, name, value, placeholder, helper, message, state, disabled, required, rowsmue-changemue-textarea.jsno catalog entry

Attribute and event lists were read out of the 40 modules in frameworks/web-components/components/; the “Module” column names the file, so any row can be checked in seconds. The Catalog entry column comes from frameworks/registry.json, which points each catalog id at one web-components source file: 36 of the 43 tags are named that way, the other 7 (mue-accordion-item, mue-avatar, mue-avatar-group, mue-radio-group, mue-tab, mue-tabpanel, mue-textarea) are registered and shown in the gallery but no catalog entry points at their file. Those links open the vanilla preview page, so its source panel prints HTML rather than a mue-* tag — the gallery above is where the element form is shown.

The 11 events

Every event is a CustomEvent that bubbles from the host, so one listener on a container is enough. Names are prefixed because a bare change would collide with the native event on the <input> inside.

mue-changeThe value changed — inputs, textareas, selects, checkboxes, radios, switches, rating, progress, tabs, date picker, dropzone. Carries the new value in event.detail.
mue-open · mue-closeOverlay lifecycle for mue-dialog, mue-modals, mue-popover, mue-dropdown, and mue-notification / mue-fab / mue-guidance on the close side.
mue-selectAn option was chosen — mue-dropdown, mue-nav-menu, mue-datatable row selection.
mue-navigateA link inside a navigation element was activated: mue-header-nav, mue-nav-menu and the three sidebar tags.
mue-sort · mue-filter · mue-expandTable interactions owned by mue-datatable — column sort, query filter, row disclosure.
mue-page-changePagination moved, with the new page on mue-pagination.
mue-removeA file was taken out of the mue-dropzone list.
mue-stepThe active step changed on mue-stepper, and on the mue-guidance and mue-fab tour controls.

What was run to say any of this

A throwaway project holding nothing but the output of mue add --framework web-components, served over http://127.0.0.1 and driven in headless Chrome. Twelve assertions, twelve passes, re-run the same day this page was written.

CheckValue the browser reported
3 tags registeredmue-button, mue-input, mue-card all defined by index.js
Light DOMshadowRoot === null on every host
Button is a buttonBUTTON, type="button", class="btn-primary", background rgb(36, 99, 235), text rgb(255, 255, 255), radius 8px
Label is boundlabel[for="mue-input-1"] matches input#mue-input-1, type="email"
Card uses the sheetclass="card card-elevated", radius 16px, background rgb(255, 255, 255)
Token resolves--color-primary-500: 221 83% 53% on :root, body rgb(248, 250, 252)
mue-change firestyping into the input delivered ["a@b.co"]
Click reaches the host1 listener call, disabled === false
No overflow at 1280pxscroll width 1280 = client width 1280, card 461px
No overflow at 390pxscroll width 390 = client width 390, card 358px
html.dark reaches thempage rgb(10, 14, 21), button rgb(95, 145, 252), card rgb(35, 42, 52)
Consoleclean — no errors, no warnings from the modules

Plus the wider pass that backs the stable label: every preview page built and rendered in Chrome and Edge at 1280px and 390px in light and dark, with axe-core runs over the same pages. See docs/framework-support.md for the full statement.

What this page does not claim

!

Not published to npm

mue-web-components is the name the folder would ship under; it is not on the registry. Installation is a file copy from a git checkout, described on the CLI page. Publishing is on the roadmap in README.md and has not happened.

!

No framework interop verified

The React and Vue ports are independent implementations of the same tokens and component classes, written separately. They do not wrap these elements and these elements do not wrap them. No mixed-framework project has been built or tested, so nothing here promises that a React app can mount <mue-card> and keep its props in sync.

!

Never run on some engines

Chrome and Edge only. Not run: Safari, Firefox, a screen reader, a real touch device, server-side rendering, and a published-package install. Custom elements and form-associated behaviour differ across engines, so a Safari result is genuinely unknown rather than assumed good.

!

Light DOM is a trade, not a win

Your document stylesheet reaches the elements — that is the benefit. The price is that document-level CSS also reaches into them, and the host rebuilds its children, so a global button { } rule can override btn-primary. The gallery is the reference for which markup each element expects.

!

The table describes the current commit

Attribute lists were harvested from frameworks/web-components/components/ on 2026-11-01. Run node tools/mue.js usage <id> --framework web-components and the tool prints what the files say today, which is the version you should trust.