Skip to content

Design System

dawu-manager uses shadcn/ui v5 with the base-nova style as its component library, built on @base-ui/react and styled with Tailwind CSS v4. This page documents the design system architecture, component conventions, and patterns used throughout the application.


Component Library

shadcn/ui v5

shadcn/ui provides accessible, composable UI primitives. Unlike traditional component libraries, shadcn/ui components are copied into the project source code (src/components/ui/), allowing full customization without forking a dependency.

Property Value
Library shadcn/ui v5
Foundation @base-ui/react (NOT Radix)
Style base-nova
CSS Tailwind CSS v4
Configuration components.json

Available Components

The following shadcn/ui components are installed and used throughout the application:

Component File Usage
Avatar ui/avatar.tsx User profile display in header
Badge ui/badge.tsx Status indicators, role labels
Button ui/button.tsx All interactive buttons
Card ui/card.tsx Node cards, stat cards, content containers
Dialog ui/dialog.tsx Confirmation dialogs, modal forms
Dropdown Menu ui/dropdown-menu.tsx User menu, action menus
Input ui/input.tsx Form fields
Label ui/label.tsx Form field labels
Select ui/select.tsx Dropdown selects
Separator ui/separator.tsx Visual dividers
Sheet ui/sheet.tsx Mobile navigation drawer
Sonner ui/sonner.tsx Toast notifications
Table ui/table.tsx Data tables
Textarea ui/textarea.tsx Multi-line text input
Checkbox ui/checkbox.tsx Multiple selection
Switch ui/switch.tsx Toggle controls
Tabs ui/tabs.tsx Tabbed content panels

Important: No asChild Prop

shadcn/ui v5 uses @base-ui/react, which does not support the asChild prop from Radix. If you need to render a custom element inside a component, use the render prop instead:

// Correct (shadcn/ui v5)
<Button render={<a href="/somewhere" />}>Click me</Button>

// Incorrect (Radix pattern, does not work)
<Button asChild><a href="/somewhere">Click me</a></Button>

Theming

CSS Variables

The design system uses CSS custom properties (variables) defined in src/app/globals.css. These control colors, spacing, border radius, and other visual properties:

:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --primary: oklch(0.205 0.085 232.4);      /* Indigo */
  --primary-foreground: oklch(0.985 0 0);
  --secondary: oklch(0.97 0 0);
  --destructive: oklch(0.577 0.245 27.325);  /* Red */
  --border: oklch(0.922 0 0);
  --ring: oklch(0.708 0 0);
  --radius: 0.625rem;
  /* ... additional variables ... */
}

Dark Mode

CSS variables include dark mode variants using the .dark class:

.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  /* ... dark variants ... */
}

Dark mode is supported through Tailwind's dark: variant. The theme can be toggled by adding or removing the dark class on the root element.


Layout Components

The sidebar (src/components/layout/sidebar.tsx) provides the primary navigation. It is collapsible on desktop and replaced by a sheet-based drawer on mobile.

Navigation items are defined as data in src/config/navigation.ts:

export const mainNavItems = [
  { title: "Overview", href: "/", icon: LayoutDashboard },
  { title: "Nodes", href: "/nodes", icon: Server },
  // ...
];

export const managementNavItems = [
  { title: "Users", href: "/users", icon: Users, roles: ["admin"] },
  { title: "Audit Log", href: "/audit", icon: FileText, roles: ["admin"] },
  // ...
];

The roles array controls visibility based on the user's role. Items without a roles array are visible to all authenticated users.

The header (src/components/layout/header.tsx) displays:

  • Mobile navigation toggle button (visible on small screens).
  • User avatar and dropdown menu (profile, logout).

Mobile Navigation

On screens narrower than the lg breakpoint (1024px), the sidebar is replaced by a sheet-based drawer triggered by the hamburger button in the header.


Application Components

Stat Card

The stat card (src/components/dashboard/stat-card.tsx) displays a single metric:

  • Icon (from Lucide React).
  • Label text.
  • Value (number or string).

Used on the dashboard overview for fleet statistics (total nodes, online, offline, degraded).

Node Card

The node card (src/components/dashboard/node-card.tsx) displays a registered BNG node:

  • Node name and location.
  • Status badge (color-coded).
  • Last seen timestamp.
  • Link to node detail page.

Status Badge

The status badge (src/components/shared/status-badge.tsx) renders a color-coded badge:

Status Color
Online Green
Offline Red
Degraded Yellow
Unknown Gray

Confirm Dialog

The confirm dialog (src/components/shared/confirm-dialog.tsx) wraps destructive actions with a confirmation step. It displays:

  • Action description.
  • Warning text.
  • Cancel and confirm buttons.
  • The confirm button uses the destructive variant (red).

Loading Skeleton

The loading skeleton (src/components/shared/loading-skeleton.tsx) provides animated placeholder content displayed while data is being fetched. Used in loading.tsx files for each page route.


Form Patterns

Forms use React Hook Form with Zod validation:

const schema = z.object({
  name: z.string().min(1, "Name is required"),
  email: z.string().email("Invalid email"),
  password: z.string().min(4, "Minimum 4 characters"),
});

const form = useForm<z.infer<typeof schema>>({
  resolver: zodResolver(schema),
  defaultValues: { name: "", email: "", password: "" },
});

Mutation Buttons

All buttons that trigger mutations (create, update, delete) display a loading spinner while the operation is in progress. The button is disabled during the mutation to prevent double-submission:

<Button type="submit" disabled={isPending}>
  {isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
  Save
</Button>

Toast Notifications

All mutations display a toast notification on completion using Sonner:

  • Success: Green toast with a success message.
  • Error: Red toast with an error message (including server-provided detail when available).
  • Validation error: Toast showing the first validation error from the Zod schema.

Responsive Design

dawu-manager uses a mobile-first responsive approach:

Breakpoint Width Layout
Default < 640px Single column, sheet navigation, stacked cards
sm 640px+ Minor spacing adjustments
md 768px+ Two-column grids where appropriate
lg 1024px+ Sidebar navigation visible, wider content area
xl 1280px+ Maximum content width

Accessibility

shadcn/ui components are built on @base-ui/react, which provides:

  • Keyboard navigation for all interactive elements.
  • ARIA attributes for screen readers.
  • Focus management for dialogs and menus.
  • Color contrast ratios meeting WCAG 2.1 AA standards.

Additional accessibility measures in dawu-manager:

  • All form inputs have associated labels.
  • Destructive actions require explicit confirmation.
  • Status badges use both color and text to convey information (not color alone).
  • Interactive elements have visible focus indicators.