v1.0.0

Getting started

BMX components are standards-based custom elements. The browser renders them, so they behave the same in plain HTML, React, Angular, Vue, Svelte, Blazor, ASP.NET MVC, or a twelve-year-old server-rendered application with no build step at all.

The one-file route

Copy bmx-components.min.js next to your pages and add one tag:

<script src="/assets/bmx-components.min.js"></script>

<bmx-button tone="primary">Save</bmx-button>

That is the whole installation. The runtime registers every <bmx-*> element and injects its own design tokens, so there is no second stylesheet to remember and nothing to configure.

Elements upgrade whenever they appear, so markup rendered later — by a templating engine, an htmx swap, a jQuery plugin — works without being told.

The bundler route

If your application already has a build step:

import { defineCustomElements } from 'bmx-webcomponents/loader';
import 'bmx-webcomponents/dist/bmx-tokens.css';

defineCustomElements();

Components are lazy-loaded on this route: an application that uses two of them downloads two, not twenty.

Attributes and properties

Simple values go through attributes, exactly as they look:

<bmx-button variant="outline" tone="danger" size="lg">Delete</bmx-button>

Anything that is not a string is a property, because an HTML attribute can only ever be a string — an array pushed through one arrives as "[object Object]", which is the single most common integration problem with any Web Component:

document.querySelector('bmx-split-button').items = [
  { id: 'save-as', label: 'Save as…' },
  { separator: true },
  { id: 'delete', label: 'Delete', danger: true },
];

The generated framework wrappers do this for you.

Events

Every component's events are CustomEvents carrying a detail:

button.addEventListener('bmxActivate', event => {
  console.log(event.detail.via); // 'pointer' | 'keyboard' | 'hold' | 'programmatic'
});

bmx-button also lets the ordinary click event through, so existing code keeps working. It is suppressed only when one of the activation guards (confirm, hold, cooldown) has decided the click does not count — which is what makes confirm work with no code at all.

Frameworks

React

import { BmxButton } from 'bmx-webcomponents-react';

<BmxButton tone="danger" confirm="Click again to delete" onBmxActivate={remove}>
  Delete
</BmxButton>

Angular

<bmx-button tone="danger" [confirm]="'Click again'" (bmxActivate)="remove()">
  Delete
</bmx-button>

Vue

<BmxButton tone="danger" confirm="Click again" @bmxActivate="remove">Delete</BmxButton>

The wrappers contain no component code. They render the tag and bridge properties and events, which is why one wrapper serves both the demo and the commercial runtime.

Server-side rendering

The components guard every document and window access, so importing them in a Node render pass is safe. They render on the client; nothing throws on the server.