Skip to content
NuvoUI

Dashboard layout

A dashboard shell with a side navigation, a page header, three stat tiles and a recent orders list. The navigation sits above the content below 768px.

Preview

Overview

  • Revenue

    $48,210

  • Orders

    1,284

  • Refund rate

    1.8%

Recent orders

  • Lina Haddad$320.00
  • Omar Siddiqui$86.50
  • Grace Okafor$1,140.00

With utility classes

The markup of the preview. Classes come from the prebuilt dist/nuvoui.min.css, or from your own build with $generate-utility-classes: true.

HTML
<div class="grid cols-12@md bg-base text-regular border rounded-lg overflow-hidden">
    <nav class="col-span-3@md col-span-2@lg bg-alternate p-4" aria-label="Dashboard">
        <p class="font-semibold m-0 mb-3">Northwind</p>
        <ul class="flex col@md gap-1 m-0 p-0 overflow-x-auto text-sm" role="list">
            <li><a class="block whitespace-nowrap bg-selected text-brand font-medium rounded-md px-3 py-2" href="/dashboard/" aria-current="page">Overview</a></li>
            <li><a class="block whitespace-nowrap text-muted hover:bg-hover hover:text-regular rounded-md px-3 py-2" href="/dashboard/orders/">Orders</a></li>
            <li><a class="block whitespace-nowrap text-muted hover:bg-hover hover:text-regular rounded-md px-3 py-2" href="/dashboard/customers/">Customers</a></li>
            <li><a class="block whitespace-nowrap text-muted hover:bg-hover hover:text-regular rounded-md px-3 py-2" href="/dashboard/settings/">Settings</a></li>
        </ul>
    </nav>
    <section class="col-span-9@md col-span-10@lg p-4 p-6@md" aria-labelledby="dashboard-title">
        <div class="flex wrap between x-center gap-3 mb-6">
            <h3 class="text-xl font-semibold m-0" id="dashboard-title">Overview</h3>
            <button class="rounded-md text-sm" type="button">Export report</button>
        </div>
        <ul class="grid cols-1 cols-3@sm gap-4 m-0 p-0" role="list">
            <li class="bg-surface border rounded-lg p-4">
                <p class="text-sm text-muted m-0">Revenue</p>
                <p class="text-2xl font-semibold m-0 mt-1">$48,210</p>
            </li>
            <li class="bg-surface border rounded-lg p-4">
                <p class="text-sm text-muted m-0">Orders</p>
                <p class="text-2xl font-semibold m-0 mt-1">1,284</p>
            </li>
            <li class="bg-surface border rounded-lg p-4">
                <p class="text-sm text-muted m-0">Refund rate</p>
                <p class="text-2xl font-semibold m-0 mt-1">1.8%</p>
            </li>
        </ul>
        <section class="bg-surface border rounded-lg mt-6" aria-labelledby="orders-title">
            <h4 class="text-base font-semibold m-0 px-4 py-3 border-b" id="orders-title">Recent orders</h4>
            <ul class="m-0 p-0 text-sm" role="list">
                <li class="flex between gap-4 px-4 py-3 border-b"><span>Lina Haddad</span><span class="text-muted">$320.00</span></li>
                <li class="flex between gap-4 px-4 py-3 border-b"><span>Omar Siddiqui</span><span class="text-muted">$86.50</span></li>
                <li class="flex between gap-4 px-4 py-3"><span>Grace Okafor</span><span class="text-muted">$1,140.00</span></li>
            </ul>
        </section>
    </section>
</div>

With apply()

The same result with BEM class names and no utility classes. @use "@nuvoui/core" as *; emits the tokens and base styles, and each apply() writes one rule.

SCSS
@use "@nuvoui/core" as *;

.dashboard {
    @include apply(
        grid, // display: grid
        cols(12) md, // 12 grid columns from md (768px) up
        bg(base), // page background, follows the theme
        text-color(regular), // regular text color, follows the theme
        border(1), // 1px border (width key 1), regular color
        rounded(lg), // corner radius: size key lg (0.75rem)
        overflow-hidden // clip content that overflows the box
    );
}

.dashboard__nav {
    @include apply(
        col-span(3) md, // span 3 of 12 columns from md (768px) up
        col-span(2) lg, // span 2 of 12 columns from lg (1024px) up
        p(4), // padding: spacing key 4 (1rem)
        bg(alternate) // alternate background, follows the theme
    );
}

.dashboard__brand {
    @include apply(
        m(0), // margin: 0
        mb(3), // bottom margin: spacing key 3 (0.75rem)
        font(semibold) // font weight: semibold (600)
    );
}

// A scrolling row below md, a column from md
.dashboard__menu {
    @include apply(
        flex, // display: flex
        col md, // flex direction: column from md (768px) up
        gap(1), // gap: spacing key 1 (0.25rem)
        m(0), // margin: 0
        p(0), // padding: 0
        overflow-x-auto, // scroll sideways when the row is too wide
        font-size(sm) // font size key sm (0.875rem)
    );
}

.dashboard__link {
    @include apply(
        block, // display: block
        px(3), // horizontal padding: spacing key 3 (0.75rem)
        py(2), // vertical padding: spacing key 2 (0.5rem)
        rounded(md), // corner radius: size key md (0.5rem)
        whitespace-nowrap, // keep the text on one line
        text-color(muted), // muted text color, follows the theme
        bg(hover) hover, // hover background on hover
        text-color(regular) hover // regular text color on hover
    );

    &[aria-current="page"] {
        @include apply(
            bg(selected), // selected item background
            text-color(brand), // brand text color
            font(medium) // font weight: medium (500)
        );
    }

    &:focus-visible {
        outline: token(border-focus-width) solid token(border-focus);
        outline-offset: token(border-focus-offset);
    }
}

.dashboard__main {
    @include apply(
        col-span(9) md, // span 9 of 12 columns from md (768px) up
        col-span(10) lg, // span 10 of 12 columns from lg (1024px) up
        p(4), // padding: spacing key 4 (1rem)
        p(6) md // padding key 6 (1.5rem) from md (768px) up
    );
}

.dashboard__header {
    @include apply(
        flex, // display: flex
        wrap, // let items wrap onto new lines
        between, // main axis: space between (justify-content)
        x-center, // cross axis: center (align-items)
        gap(3), // gap: spacing key 3 (0.75rem)
        mb(6) // bottom margin: spacing key 6 (1.5rem)
    );
}

.dashboard__title {
    @include apply(
        m(0), // margin: 0
        font-size(xl), // font size key xl (1.5rem)
        font(semibold) // font weight: semibold (600)
    );
}

.dashboard__button {
    @include apply(
        rounded(md), // corner radius: size key md (0.5rem)
        font-size(sm) // font size key sm (0.875rem)
    );
}

.dashboard__stats {
    @include apply(
        grid, // display: grid
        cols(1), // one grid column
        cols(3) sm, // three columns from sm (640px) up
        gap(4), // gap: spacing key 4 (1rem)
        m(0), // margin: 0
        p(0) // padding: 0
    );
}

.dashboard__stat {
    @include apply(
        p(4), // padding: spacing key 4 (1rem)
        bg(surface), // surface background, follows the theme
        border(1), // 1px border (width key 1), regular color
        rounded(lg) // corner radius: size key lg (0.75rem)
    );
}

.dashboard__label {
    @include apply(
        m(0), // margin: 0
        font-size(sm), // font size key sm (0.875rem)
        text-color(muted) // muted text color, follows the theme
    );
}

.dashboard__value {
    @include apply(
        m(0), // margin: 0
        mt(1), // top margin: spacing key 1 (0.25rem)
        font-size(2xl), // font size key 2xl (1.75rem)
        font(semibold) // font weight: semibold (600)
    );
}

.dashboard__panel {
    @include apply(
        mt(6), // top margin: spacing key 6 (1.5rem)
        bg(surface), // surface background, follows the theme
        border(1), // 1px border (width key 1), regular color
        rounded(lg) // corner radius: size key lg (0.75rem)
    );
}

.dashboard__panel-title {
    @include apply(
        m(0), // margin: 0
        px(4), // horizontal padding: spacing key 4 (1rem)
        py(3), // vertical padding: spacing key 3 (0.75rem)
        font-size(base), // font size key base (1rem)
        font(semibold), // font weight: semibold (600)
        border-bottom(1) // 1px bottom border (width key 1), regular color
    );
}

.dashboard__orders {
    @include apply(
        m(0), // margin: 0
        p(0), // padding: 0
        font-size(sm) // font size key sm (0.875rem)
    );
}

.dashboard__order {
    @include apply(
        flex, // display: flex
        between, // main axis: space between (justify-content)
        gap(4), // gap: spacing key 4 (1rem)
        px(4), // horizontal padding: spacing key 4 (1rem)
        py(3), // vertical padding: spacing key 3 (0.75rem)
        border-bottom(1), // 1px bottom border (width key 1), regular color
        border-bottom(0) last-child // no bottom border when it is the last child
    );
}

.dashboard__amount {
    @include apply(text-color(muted)); // muted text color, follows the theme
}
HTML
<div class="dashboard">
    <nav class="dashboard__nav" aria-label="Dashboard">
        <p class="dashboard__brand">Northwind</p>
        <ul class="dashboard__menu" role="list">
            <li><a class="dashboard__link" href="/dashboard/" aria-current="page">Overview</a></li>
            <li><a class="dashboard__link" href="/dashboard/orders/">Orders</a></li>
            <li><a class="dashboard__link" href="/dashboard/customers/">Customers</a></li>
            <li><a class="dashboard__link" href="/dashboard/settings/">Settings</a></li>
        </ul>
    </nav>
    <section class="dashboard__main" aria-labelledby="dashboard-title">
        <div class="dashboard__header">
            <h3 class="dashboard__title" id="dashboard-title">Overview</h3>
            <button class="dashboard__button" type="button">Export report</button>
        </div>
        <ul class="dashboard__stats" role="list">
            <li class="dashboard__stat">
                <p class="dashboard__label">Revenue</p>
                <p class="dashboard__value">$48,210</p>
            </li>
            <li class="dashboard__stat">
                <p class="dashboard__label">Orders</p>
                <p class="dashboard__value">1,284</p>
            </li>
            <li class="dashboard__stat">
                <p class="dashboard__label">Refund rate</p>
                <p class="dashboard__value">1.8%</p>
            </li>
        </ul>
        <section class="dashboard__panel" aria-labelledby="orders-title">
            <h4 class="dashboard__panel-title" id="orders-title">Recent orders</h4>
            <ul class="dashboard__orders" role="list">
                <li class="dashboard__order"><span>Lina Haddad</span><span class="dashboard__amount">$320.00</span></li>
                <li class="dashboard__order"><span>Omar Siddiqui</span><span class="dashboard__amount">$86.50</span></li>
                <li class="dashboard__order"><span>Grace Okafor</span><span class="dashboard__amount">$1,140.00</span></li>
            </ul>
        </section>
    </section>
</div>

Tokens used

Every color comes from a theme token, so the recipe follows the light, dark and inverse themes with no extra CSS.

bg-base, bg-surface, bg-alternate
Three surface levels: the page, the tiles and panel, and the side navigation.
text-regular, text-muted
Values and labels.
border-regular
Tile, panel and row rules.
bg-selected, text-brand
The current page in the navigation, matched on aria-current="page" in the SCSS version.
bg-hover
Navigation link hover.
brand, text-on-brand, border-focus
The export button and the focus ring, from the base layer.