Skip to content

Styling Custom UI

Every extension mechanism in this section hands you a React component and gets out of the way — a custom view, a home page, an entity view, a slot. What none of them says is what to build it out of.

The answer is: the same parts the admin is built from. A custom view is still an admin view. It sits inside the same shell, beside the same tables, under the same theme toggle — so it should use the same components, the same type scale and the same colour tokens.

The alternative is inventing a second design language inside the same app. That is the common failure, and it does not merely look inconsistent — it breaks. A hand-written color: #111 renders invisible the moment someone switches to the dark theme, and no test catches it.

Import components from @rebasepro/ui. Reach for a raw <div> and a class only for layout.

import { Alert, Button, Card, Chip, Typography } from "@rebasepro/ui";
export function DashboardView() {
return (
<div className="p-8 max-w-5xl mx-auto flex flex-col gap-8">
<Typography variant="h4">Outreach</Typography>
<Typography variant="body2" color="secondary">
What ran last night, and what is waiting for you.
</Typography>
<Card className="p-4 flex flex-col gap-1">
<Typography variant="h5" className="mb-0 tabular-nums">128</Typography>
<Typography variant="subtitle2" className="mb-0">Signals</Typography>
<Typography variant="caption" color="secondary" className="mb-0">12 approved</Typography>
</Card>
<Alert color="warning">Delivery is not configured, so nothing can be sent.</Alert>
</div>
);
}

Every component in the kit is catalogued under UI components with its real props, generated from the source. Check there before hand-rolling: Card, Chip, Badge, Alert, Button, Typography, Paper, Container, Table, Tooltip, Dialog and about forty more already exist.

The theme is a set of CSS variables exposed as Tailwind utilities. Use them, and pair every light value with a dark: one:

Use Class
Body text text-surface-900 dark:text-surface-100
Secondary text text-surface-600 dark:text-surface-400 — or just <Typography color="secondary">
Panel background bg-surface-accent-50 dark:bg-surface-800
Borders border-surface-200 dark:border-surface-700
Accent text-primary / bg-primary (#0070F4)

Two rules that come from real breakage:

  • Never write a colour literal. #111, rgba(128,128,128,.28), white — each one is correct in exactly one theme. A page whose numbers were color: var(--fg, #111) rendered black-on-black for every dark-theme user, and looked perfect to the person who wrote it.
  • Never set a colour a component already sets. <Typography> picks the right foreground for the theme. Overriding it with a class is how a heading ends up the only element on the page that ignores the theme.

Typography carries the whole scale — h1h6, subtitle1/subtitle2, body1/body2, caption, label. Use variant, not a font-size class. The scale already encodes the tracking each tier needs (--tracking-display at ≥30px, --tracking-title at 20–24px, --tracking-heading below that), which a text-[27px] does not.

Product UI does not go below text-xs. The text-2xs and text-3xs tiers exist for marketing pages only.

Custom UI needs the theme’s CSS and Tailwind pointed at the packages, or the utility classes used inside @rebasepro/ui never get generated:

@import "tailwindcss";
@import "@rebasepro/ui/index.css" layer(base);
/* Without this, Tailwind never scans the kit's own classes. */
@source "../node_modules/@rebasepro";
@custom-variant dark (&:where(.dark, .dark *));

rebase init writes this for you. If your custom view renders unstyled, this is the first thing to check.

Before shipping a custom view:

  • No colour literals — every colour is a token or comes from a component.
  • Every bg-, text- and border- has a dark: counterpart.
  • Text is <Typography variant=…>, not a font-size class.
  • Containers are Card / Paper, not a <div> with a hand-written border.
  • Toggle the theme and look at the page. That is the whole test, and it takes five seconds.