Catalog · Charts
Dashboard
The view of an application at a glance: its title and main action, then a grid of panels that fills the room the view has.
Fits best: app — the first view of an application
Example
A dashboard with a title and two actions, then three panels.
Overview
-
Sales this month
The most important number gets the room of two columns, when the view has it.
Open the sales report -
Stock alerts
Three products are below their minimum.
Review stock -
Open orders
Twelve orders wait for a label.
See orders
The dashboard at the width of this column. In a page it is the <main>, and the title is the page's <h1>: here this page already has its own, so the title is a paragraph and the panels' titles are <h3>.
The grid answers to the room of the dashboard, not to the window's.
With data
The same view with four panels that show data: a figure, a bar list, a line chart and a stacked bar, each over a native list or table that stays in the page. The titles, the panels and the figures are made up for this example. What a panel shows, what it is called and where its numbers come from are yours to write: LUZ gives the pieces, not a finished dashboard.
Overview (sample data)
-
Orders this month
1,204
12 % more than last month
-
Orders by channel
-
Orders over time
Orders per month, January to June Month Orders Jan 820 Feb 905 Mar 870 Apr 1,010 May 1,075 Jun 1,204 -
Orders by status
The wide panel is the line chart: it takes two columns when the view has the room.
Known issues
None known.
Code
Copy the markup
import "@luz-ds/catalog/components/dashboard.css";
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/catalog@0.3.0/dist/components/dashboard.min.css" integrity="sha384-G+VBlP1ufPGoYlRgylA/N+xI4iqLCarfUXTGrPbwteTmtaqhi7iVz7gXNXkK/gVy" crossorigin="anonymous">
The view: a title row, then the panels. The title row is a <div>, and each panel starts with its heading.
<main class="luz-dashboard" id="main-content" tabindex="-1">
<div class="luz-dashboard__head">
<h1 class="luz-dashboard__title">Overview</h1>
<div class="o-actions">
<a class="luz-btn luz-btn--primary" href="/reports/new/">New report</a>
<a class="luz-btn luz-btn--secondary" href="/export/">Export data</a>
</div>
</div>
<ul class="o-grid o-grid--auto">
<li class="luz-card luz-dashboard__panel luz-dashboard__panel--wide">
<h2 class="luz-card__title">Sales this month</h2>
<p class="luz-card__body">…</p>
</li>
<li class="luz-card luz-dashboard__panel">
<h2 class="luz-card__title">Stock alerts</h2>
<p class="luz-card__body">…</p>
</li>
</ul>
</main>
A panel that shows data holds one of the data pieces (a bar list, a column chart, a stacked bar, a heat map) or the chart, each over a native list or table that stays in the page. The figures here are samples: the numbers, the titles and the panels are yours.
<!-- Sample figures: write your own -->
<ul class="o-grid o-grid--auto">
<li class="luz-card luz-dashboard__panel">
<h2 class="luz-card__title">Orders this month</h2>
<div class="luz-stat">
<p class="luz-stat__value">1,204</p>
<p class="luz-stat__label">12 % more than last month</p>
</div>
</li>
<li class="luz-card luz-dashboard__panel">
<h2 class="luz-card__title" id="by-channel">Orders by channel</h2>
<figure class="luz-bar-list" aria-labelledby="by-channel">
<ul class="luz-bar-list__rows">
<li class="luz-bar-list__row">
<span class="luz-bar-list__name">Online</span>
<span class="luz-bar-list__figure">640</span>
<meter class="luz-bar-list__meter" min="0" max="1204" value="640" aria-hidden="true">640 of 1,204</meter>
</li>
</ul>
</figure>
</li>
<li class="luz-card luz-dashboard__panel luz-dashboard__panel--wide">
<h2 class="luz-card__title">Orders over time</h2>
<luz-chart type="line" data-luz-label-table="Show table" data-luz-label-chart="Show chart">
<table class="luz-table">
<caption>Orders per month, January to June</caption>
<thead><tr><th scope="col">Month</th><th scope="col">Orders</th></tr></thead>
<tbody>
<tr><th scope="row">Jan</th><td data-label="Orders"><data value="820">820</data></td></tr>
<!-- one tr for each month -->
</tbody>
</table>
</luz-chart>
</li>
<li class="luz-card luz-dashboard__panel">
<h2 class="luz-card__title" id="by-status">Orders by status</h2>
<figure class="luz-stacked-bar" aria-labelledby="by-status">
<div class="luz-stacked-bar__bar" aria-hidden="true">
<span class="luz-stacked-bar__part" style="--v: 62"></span>
<span class="luz-stacked-bar__part" style="--v: 38"></span>
</div>
<ul class="luz-stacked-bar__legend">
<li class="luz-stacked-bar__item"><span class="luz-stacked-bar__name">Delivered</span> <span class="luz-stacked-bar__figure">62 %</span></li>
<li class="luz-stacked-bar__item"><span class="luz-stacked-bar__name">Other</span> <span class="luz-stacked-bar__figure">38 %</span></li>
</ul>
</figure>
</li>
</ul>
Variants and options
One modifier, for the panel. The dashboard is a class of app on a native <main>; the grid and the card are core's.
| Class | Effect |
|---|---|
.luz-dashboard | On <main>: the view. 32 px of padding (16 px at 640 px and below), 24 px between its rows, and the container of its own size query |
.luz-dashboard | On a <div>: the title at the start and the actions at the end, on one row that wraps; at 640 px and below, a column |
.luz-dashboard | The view's one <h1>: 2xl size, semibold, in the text colour; xl at 640 px and below |
.luz-dashboard | On a .luz-card in the grid: it can shrink, so a wide child never widens its column |
.luz-dashboard | Two columns, when the dashboard is at least 40 rem wide; one column below, so it never asks for a column that is not there |
.o-grid.o-grid (core) | On the <ul>: as many equal columns as fit, none narrower than 18 rem |
.o-actions (core) | Two or three related actions: a row that wraps, a column of full-width actions at 640 px and below |
How it works
CSS from components/dashboard.css of @luz-ds/catalog on core's grid and card. No JavaScript.
- The dashboard sets
--luz-grid-min: 18remfor core's.o-grid--auto, so a column is never narrower than 18 rem and the number of columns follows the room: one at 320 px, two at 768 px, and three at 1280 px beside a sidebar. - The dashboard is a size container. The wide panel asks how wide the dashboard is, not the window, so it fits with a sidebar and without one. From 40 rem two columns always exist (two of 18 rem and a gap), and at 200% text both are in rem, so it never overflows.
- The panels keep the order of the markup. There is no dense packing and no
order: a wide panel that does not fit in what is left of a row starts the next one, and the gap stays. Reading order, focus order and what is drawn are the same. - A panel can shrink, so a long line of code or a table inside it scrolls in its own wrapper and does not widen the column.
- A panel with data uses the pieces that already exist: the stat for one figure, bar list, column chart, stacked bar and heat map, and the chart for a trend. Each draws a list or a table you wrote, and that list or table stays in the page. The bar list, the column chart, the stacked bar and the heat map are separate stylesheets: link each one you use after
luz.css. - The data is yours. LUZ does not fetch, count or invent figures, and the example's titles and numbers are samples to replace.
- Empty, loading and failed panels are the panel's own content: a sentence and a link, core's progress or alert. There is no skeleton class in 0.1.
Without JavaScript
Nothing changes: the grid, the container query and the cards are CSS.
Keyboard and ARIA
- Order: the order of the source: the title row's actions, then each panel in turn. A panel that is a card with a link inside is reached by that link.
- Headings: the view has one
<h1>, in the title row, and each panel starts with an<h2>. The title row is a<div>, not a<header>. - List: the panels are a
<ul>of<li>: a screen reader says how many there are. Safari can drop the list role from a list without markers; the headings still lead through the panels. - Actions: an action that goes somewhere is a link (
<a href>); one that does something on this page is a<button type="button">. Never an<a>withouthref. - Data: a panel's figures are text first (a name and a number, or a table), so every panel reads with no picture. The chart's own button swaps the drawing for its table, and a change is never told by colour alone: a comparison says "12 % more than last month" in words.
- Landmarks: the dashboard is the page's
<main>, the sibling of the header. It takes the skip link's target, withtabindex="-1".
Contrast and focus are in the Design tab.
Design
When to use
Use the dashboard for a view that shows several things about the same subject, each short enough to read at a glance and each leading to its own page.
- An overview, a status page or a home view, where the person checks and then acts on one thing.
- The most important panel first, wide: it gets the room when the view has it.
When not to use
Do not use it for a view that is one long thing: a table, a form or a document are not panels.
- One primary action per view, in the title row, never in a panel.
- Do not make a panel taller than its neighbours with row spans or dense packing: it moves a panel before the ones that come first in the markup.
- For a plain grid of cards in a page of content, use core's grid and card.
Do and don't
Writing the content
The title names the view; each panel names one subject and says its state in a sentence.
- Title: a noun the person knows from the sidebar, "Overview", "Reports". The same words as the sidebar's link.
- Main action: a verb and what it makes, "New report". One per view.
- Panel title: the subject, "Stock alerts". Under it, one sentence with the number: "Three products are below their minimum."
- Link: where it goes, "Review stock", not "More" or "Click here".
- Figures: only real ones. A sample is marked as a sample in the page that shows it, and a made-up number never ships as a finished dashboard.
Contrast and focus
The title sits on the page surface and the panels' text on the card's surface.
| Part | Colour on background | Light | Dark | High contrast | Needs |
|---|---|---|---|---|---|
View title | --luz-text-primary on --luz-surface-page | 15.8:1 | 15.1:1 | 21.0:1 | 4.5:1 |
Panel title | --luz-text-primary on --luz-surface | 16.5:1 | 14.0:1 | 21.0:1 | 4.5:1 |
Panel text | --luz-text-secondary on --luz-surface | 6.4:1 | 6.9:1 | 19.3:1 | 4.5:1 |
Focus ring | --luz-focus-ring on --luz-surface | 5.9:1 | 7.2:1 | 21.0:1 | 3:1 |
Measured from the token values of each mode, rounded down. Needs is the WCAG 2.2 AA minimum: 4.5:1 for text, 3:1 for focus rings, borders and icons.
- Focus: every link and button in a panel keeps core's ring, 2 px of
--luz-focus-ring. A panel whose title is a link takes the ring on the whole card. - Edges: a card's border (
--luz-divider) is decorative; its text carries the meaning. Forced colours keep the border.
Tokens it uses
The 7 tokens that the .luz-dashboard rules of catalog’s components/dashboard.css read, by family. Each family links to its page, with the values in every mode. In Figma they are variables with the same names: see Tokens in Figma.
- Spacing and sizing
--luz-space-lg,--luz-space-md,--luz-space-sm,--luz-space-xl- Typography
--luz-text-2xl,--luz-text-xl
Keyboard and ARIA are in the Code tab.