Skip to main content

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.

  • 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

    • Online640
    • In store410
    • Phone154
  • Orders over time

    Orders per month, January to June
    MonthOrders
    Jan820
    Feb905
    Mar870
    Apr1,010
    May1,075
    Jun1,204
  • Orders by status

    • Delivered 62 %
    • In transit 21 %
    • Delayed 9 %
    • Returned 8 %

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

Bundler
import "@luz-ds/catalog/components/dashboard.css";
HTML
<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.

HTML
<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.

HTML
<!-- 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.

ClassEffect
.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__head
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__title
The view's one <h1>: 2xl size, semibold, in the text colour; xl at 640 px and below
.luz-dashboard__panel
On a .luz-card in the grid: it can shrink, so a wide child never widens its column
.luz-dashboard__panel--wide
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--auto (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: 18rem for 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> without href.
  • 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, with tabindex="-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

Do Put the title and the one main action in the first row, and one short thing in each panel, under its own heading.
Don't Give every panel a primary button: the person cannot tell which thing to do first.

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.

PartColour on backgroundLightDarkHigh contrastNeeds
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.