8 · Project: a dashboard (advanced)

Lesson 8 of 8 — dynamic styles from state, SSR-safe classes, garbage collection and a data-dense layout.

3 min readv1

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)#

TSX
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:

TSX
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:

TSX
// 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#

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

TSX
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:

TSX
<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:

Next steps#