<bmx-dashboard>
A dashboard layout: tiles on a grid of columns that the reader can drag, resize, expand and remove, with the other tiles moving out of the way and closing up. Each tile holds an element of the page's own - a chart, a gauge, a grid, anything - and the layout is JSON to save and restore.
13 properties · 3 events · 5 methods · 5 parts
Example
1,284
This week
Show markup
<div class="row" style="gap: 0.75rem; flex-wrap: wrap; align-items: center; margin-block-end: 0.75rem">
<label><input type="checkbox" id="ex-db-edit" checked> Arrange</label>
<bmx-button variant="outline" tone="neutral" id="ex-db-add">Add a note</bmx-button>
<bmx-button variant="outline" tone="neutral" id="ex-db-reset">Reset the layout</bmx-button>
</div>
<bmx-dashboard id="ex-db" editable removable columns="12" row-height="70" label="Operations overview">
<section data-tile="revenue" data-title="Revenue this year" data-w="8" data-h="4">
<bmx-chart id="ex-db-chart" type="column" label="Revenue and cost, thousands" style="--bmx-chart-height: 10rem"></bmx-chart>
</section>
<section data-tile="cpu" data-title="CPU" data-w="4" data-h="4" style="display: grid; place-items: center">
<bmx-gauge id="ex-db-cpu" value="72" unit="%" ranges="0-60:success:Normal,60-85:warning:Busy,85-:danger:Overloaded" target="80" style="--bmx-gauge-size: 12rem"></bmx-gauge>
</section>
<section data-tile="disk" data-title="Storage" data-w="4" data-h="2">
<bmx-gauge type="linear" value="640" max="1000" unit=" GB" ranges="0-700:success,700-900:warning,900-:danger" label="Disk used"></bmx-gauge>
</section>
<section data-tile="sales" data-title="Sales against target" data-w="4" data-h="2">
<bmx-gauge type="bullet" value="270" max="400" target="300" format="currency:GBP" ranges="0-200:neutral:Poor,200-300:neutral:Fair,300-:neutral:Good" label="Quarter to date (thousands)"></bmx-gauge>
</section>
<section data-tile="signups" data-title="Sign-ups" data-w="4" data-h="2">
<p style="margin: 0; font-size: 1.75rem; font-weight: 600">1,284</p>
<p style="margin: 0">This week <bmx-sparkline values="140,162,158,171,190,203,260" label="Sign-ups, last seven days"></bmx-sparkline></p>
</section>
<section data-tile="banner" data-locked data-w="12" data-h="1" style="display: flex; align-items: center; gap: 0.5rem">
<strong>All systems running.</strong> <span>This tile is locked: the others move around it.</span>
</section>
</bmx-dashboard>
<p class="note" id="ex-db-out" role="status"></p>
<script type="module">
await customElements.whenDefined('bmx-dashboard');
const db = document.getElementById('ex-db');
const out = document.getElementById('ex-db-out');
const initial = [
{ id: 'banner', x: 0, y: 0, w: 12, h: 1, locked: true },
{ id: 'revenue', x: 0, y: 1, w: 8, h: 4 },
{ id: 'cpu', x: 8, y: 1, w: 4, h: 4 },
{ id: 'disk', x: 0, y: 5, w: 4, h: 2 },
{ id: 'sales', x: 4, y: 5, w: 4, h: 2 },
{ id: 'signups', x: 8, y: 5, w: 4, h: 2 },
];
db.layout = initial;
await customElements.whenDefined('bmx-chart');
const chart = document.getElementById('ex-db-chart');
chart.data = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep'].map((month, i) => ({ month, revenue: 120 + ((i * 37) % 80), cost: 90 + ((i * 23) % 50) }));
chart.xField = 'month';
chart.yFields = ['revenue', 'cost'];
db.addEventListener('bmxDashboardChange', e => {
out.textContent = `Layout ${e.detail.kind === 'set' ? 'set' : `changed (${e.detail.kind} ${e.detail.id})`}: ${JSON.stringify(e.detail.layout.map(({ id, x, y, w, h }) => ({ id, x, y, w, h })))}`;
});
document.getElementById('ex-db-edit').addEventListener('change', e => (db.editable = e.target.checked));
document.getElementById('ex-db-reset').addEventListener('click', () => db.setLayout(initial));
let notes = 0;
document.getElementById('ex-db-add').addEventListener('click', () => {
notes += 1;
const note = document.createElement('section');
note.dataset.tile = `note-${notes}`;
note.dataset.title = `Note ${notes}`;
note.textContent = 'A tile added from script: it takes the first free place.';
db.addTile(note, { w: 4, h: 2 });
});
setInterval(() => {
const cpu = document.getElementById('ex-db-cpu');
if (cpu) cpu.value = Math.round(45 + Math.random() * 50);
}, 4000);
</script>
A tile's place comes from layout ([{ id, x, y, w, h }]), or the first
free place for its data-w by data-h. On a narrower screen the columns
shrink and the tiles follow; on a phone they stack in reading order.
Keyboard: on a tile's grip, the arrow keys move it a cell at a time and
Shift with them resizes it; Delete removes it when removable. Escape
restores an expanded tile.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
autosave |
autosave |
string |
— | A name to keep the reader's layout under in this browser, and restore it from next time (over layout; setLayout() replaces it). |
columns |
columns |
number |
12 |
Columns of the grid at full width. |
compact |
compact |
BmxDashboardCompact |
'vertical' |
How tiles close up after a change: vertical (float up, default) or none. |
editable |
editable |
boolean |
false |
The reader can move and resize tiles. |
gap |
gap |
number |
12 |
The space between tiles, in pixels. |
label |
label |
string |
— | The dashboard's accessible name. |
layout |
layout |
BmxDashboardItem[] | string |
[] |
Every tile's place and size: [{ id, x, y, w, h, minW, minH, maxW, maxH, locked }], as a property or JSON. Changes write back here. |
maximizable |
maximizable |
boolean |
true |
Tiles with a title have a button to fill the dashboard. |
minColumnWidth |
min-column-width |
number |
64 |
The narrowest a column may be, in pixels: below it the grid has fewer columns and the tiles follow. |
removable |
removable |
boolean |
false |
With editable, tiles have a button to remove them. |
rowHeight |
row-height |
number |
80 |
How tall a row is, in pixels. |
stackBelow |
stack-below |
number |
560 |
Below this width, in pixels, the tiles stack in one column. |
strings |
strings |
Partial<BmxDashboardStrings> | string |
— | Replacements for the dashboard's wording, as a property or JSON. |
Events
| Event | Detail | Description |
|---|---|---|
bmxDashboardChange |
BmxDashboardChangeDetail |
The layout changed. |
bmxDashboardExpand |
BmxDashboardExpandDetail |
A tile was expanded to fill the dashboard, or restored. |
bmxDashboardRemove |
BmxDashboardRemoveDetail |
The reader is removing a tile. Cancel it to keep the tile. |
Methods
| Method | Signature | Description |
|---|---|---|
addTile |
addTile(element: HTMLElement, place?: Partial<Omit<BmxDashboardItem, "id">>) => Promise<string> |
Adds an element as a tile, at a place or the first free one. Gives the tile's id. |
expandTile |
expandTile(id: string | null) => Promise<void> |
Makes a tile fill the dashboard, or (with null) restores it. |
getLayout |
getLayout() => Promise<BmxDashboardItem[]> |
Every tile's place and size. |
removeTile |
removeTile(id: string) => Promise<boolean> |
Removes a tile and its element. Whether it was removed (bmxDashboardRemove can be cancelled). |
setLayout |
setLayout(layout: BmxDashboardItem[] | string) => Promise<void> |
Replaces the layout. |
CSS shadow parts
| Part | Description |
|---|---|
grid |
the area the tiles sit in. |
resize-handle |
the corner a tile is resized by. |
tile |
a tile. |
tile-body |
what a tile holds. |
tile-header |
a tile's title and buttons. |
CSS custom properties
| Property | Description |
|---|---|
--bmx-dashboard-accent |
The drop place and the resize corner. |
--bmx-dashboard-tile-background |
Behind a tile. |
--bmx-dashboard-tile-border |
A tile's edge. |
--bmx-dashboard-tile-padding |
The space inside a tile's body. |
--bmx-dashboard-tile-radius |
A tile's corners. |
--bmx-dashboard-tile-shadow |
A tile's shadow. |