8 · Project: a dashboard (advanced)
Lesson 8 of 8 — dynamic styles from state, SSR-safe classes, garbage collection and a data-dense layout.
Dashboards stress a styling system: values come from data, layouts are dense, and the page lives for hours. This lesson uses React; every idea maps directly to the Vue, Svelte and Angular adapters.
You will learn: static vs dynamic rules · useNib · styling from data · keeping long-lived apps bounded.
Layout shell (static — extracted at build time)#
export function Shell({ children }: { children: React.ReactNode }) {
return (
<div nib="d=grid; gtc=1fr; lg:gtc=248px 1fr; mnh=100vh; bgc=#f6f7fb; dark:bgc=#080c14">
<aside nib="d=none; lg:d=flex; flxd=column; g=4px; p=16px; bdr=1px solid $color.border; dark:bdr=1px solid #1e293b">
<NavLinks />
</aside>
<main nib="p=16px; md:p=32px; mnw=0">{children}</main>
</div>
);
}All of these are string literals, so the build plugin turns them into CSS before the page loads.
Styling from data (dynamic — generated at runtime)#
A progress bar whose width and colour depend on a value cannot be known at build time. Use useNib:
import { useNib } from "@nibcss/react";
export function Progress({ value }: { value: number }) {
const colour = value > 80 ? "#16a34a" : value > 40 ? "#f59e0b" : "#ef4444";
const bar = useNib(`h=100%; bdrd=999px; ts=width .3s; w=${value}%; bgc=${colour}`);
return (
<div nib="h=8px; bdrd=999px; bgc=#e2e8f0; dark:bgc=#1e293b; ovf=hidden">
<div className={bar} />
</div>
);
}useNib returns a class name synchronously during render — on the server too — so the element is never
committed without its class.
A status badge that maps data to a recipe#
When the set of values is small, prefer recipes over interpolated colours — they compile at build time:
// badge-green / badge-amber / badge-red are recipes in nibcss.config.ts
const STATUS = { paid: "badge-green", pending: "badge-amber", failed: "badge-red" } as const;
export function Status({ value }: { value: keyof typeof STATUS }) {
return <span className={useNib(STATUS[value])}>{value}</span>;
}A data table#
| Customer | Amount | Status |
|---|---|---|
| Globex | $1,280.00 | Paid |
| Initech | $420.50 | Pending |
<table nib="w=100%; mnw=360px; bdcps=collapse; fts=14px; bgc=#fff; cl=#0f172a; bdrd=12px; ovf=hidden; bd=1px solid #e2e8f0; dark:bgc=#0f172a; dark:cl=#e2e8f0; dark:bd=1px solid #1e293b">
<thead nib="bgc=#f8fafc; dark:bgc=#131b2e">
<tr>
<th nib="txa=left; p=10px 14px; fts=12px; txt=uppercase; lts=.04em; cl=#64748b">Customer</th>
<th nib="txa=right; p=10px 14px; fts=12px; txt=uppercase; lts=.04em; cl=#64748b">Amount</th>
<th nib="txa=left; p=10px 14px; fts=12px; txt=uppercase; lts=.04em; cl=#64748b">Status</th>
</tr>
</thead>
<tbody>
<tr nib="bdt=1px solid #e2e8f0; hover:bgc=#f8fafc; dark:bdt=1px solid #1e293b; dark:hover:bgc=#131b2e">
<td nib="p=10px 14px">Globex</td><td nib="p=10px 14px; txa=right; font-variant-numeric=tabular-nums">$1,280.00</td>
<td nib="p=10px 14px"><span nib="p=2px 8px; bdrd=999px; fts=12px; ftw=600; bgc=#dcfce7; cl=#166534">Paid</span></td>
</tr>
<tr nib="bdt=1px solid #e2e8f0; hover:bgc=#f8fafc; dark:bdt=1px solid #1e293b; dark:hover:bgc=#131b2e">
<td nib="p=10px 14px">Initech</td><td nib="p=10px 14px; txa=right">$420.50</td>
<td nib="p=10px 14px"><span nib="p=2px 8px; bdrd=999px; fts=12px; ftw=600; bgc=#fef3c7; cl=#92400e">Pending</span></td>
</tr>
</tbody>
</table>Keeping a long-lived app bounded#
Every distinct dynamic rule is one CSS rule. A chart that interpolates thousands of widths would grow the stylesheet forever, so the runtime garbage-collects rules whose class is no longer on any element. It is on by default; tune it when you create many one-off values:
import { NibProvider } from "@nibcss/react";
import config from "@nibcss/config";
<NibProvider {...config} gc={{ everyFlushes: 30, maxRules: 2000 }}>
<App />
</NibProvider>For truly continuous values (animations, drag positions), use an inline style or a CSS variable instead:
<div nib="tf=translateX(var(--x)); ts=transform .1s" style={{ "--x": `${x}px` } as React.CSSProperties} />You finished the course#
You can now build complete interfaces with NibCSS. From here, the reference sections cover every detail: