Skip to main content

UI Framework & Theming

The frontend of this project is built on a modern design system that leverages Tailwind CSS, Shadcn UI primitives, and a custom theming engine. It provides a consistent look and feel across the application while supporting dark mode, responsive layouts, and complex data presentation.

Global Theming System

The application uses a centralized theming system managed by the ThemeProvider in frontend/src/components/theme-provider.tsx. This provider manages three theme states: light, dark, and system.

Theme Provider and Hook

The ThemeProvider uses React Context to broadcast the current theme and a setTheme function to the entire component tree. It persists the user's preference in localStorage using a configurable key (defaulting to vite-ui-theme).

// frontend/src/components/theme-provider.tsx

export function ThemeProvider({
children,
defaultTheme = "system",
storageKey = "vite-ui-theme",
...props
}: ThemeProviderProps) {
const [theme, setTheme] = useState<Theme>(
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
)

// Synchronizes the 'dark' or 'light' class on the document root
const updateTheme = useCallback((newTheme: Theme) => {
const root = window.document.documentElement
root.classList.remove("light", "dark")

if (newTheme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light"
root.classList.add(systemTheme)
return
}
root.classList.add(newTheme)
}, [])

// ... effect logic for system preference changes
}

Developers can access and modify the theme using the useTheme hook:

const { theme, setTheme, resolvedTheme } = useTheme()

CSS Variables and OKLCH

The visual styles are defined in frontend/src/index.css using the OKLCH color space, which provides better perceptual uniformity. The theme variables are mapped to Tailwind classes in the @theme block.

/* frontend/src/index.css */
:root {
--background: oklch(1 0 0);
--primary: oklch(0.5982 0.10687 182.4689);
/* ... */
}

.dark {
--background: oklch(0.145 0 0);
--primary: oklch(0.65 0.10687 182.4689);
}

UI Primitives

The project uses Shadcn UI components located in frontend/src/components/ui/. These components are built on Radix UI primitives and styled with Tailwind CSS.

Component Variants (CVA)

Most UI primitives use class-variance-authority (CVA) to manage styles. For example, the Button component in frontend/src/components/ui/button.tsx defines multiple variants and sizes:

const buttonVariants = cva(
"inline-flex items-center justify-center ... transition-all",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-white hover:bg-destructive/90 ...",
outline: "border bg-background shadow-xs ...",
// ...
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)

Layout Components

The application structure is defined by two primary layout patterns: authentication and the main application dashboard.

AuthLayout

The AuthLayout (frontend/src/components/Common/AuthLayout.tsx) provides a split-screen design for login and registration pages. It includes:

  • A branded side panel with the Logo.
  • A centered content area for forms.
  • A theme switcher (Appearance) and a Footer.

AppSidebar

The main navigation is handled by AppSidebar (frontend/src/components/Sidebar/AppSidebar.tsx). It uses Shadcn's sidebar primitives and dynamically adjusts navigation items based on the user's permissions:

export function AppSidebar() {
const { user: currentUser } = useAuth()

const items = currentUser?.is_superuser
? [...baseItems, { icon: Users, title: "Admin", path: "/admin" }]
: baseItems

return (
<Sidebar collapsible="icon">
<SidebarHeader>
<Logo variant="responsive" />
</SidebarHeader>
<SidebarContent>
<Main items={items} />
</SidebarContent>
<SidebarFooter>
<SidebarAppearance />
<User user={currentUser} />
</SidebarFooter>
</Sidebar>
)
}

Data Presentation

For displaying lists of resources (like Users or Items), the project provides a reusable DataTable component in frontend/src/components/Common/DataTable.tsx.

DataTable Implementation

This component wraps @tanstack/react-table and provides built-in:

  • Pagination: Controls for navigating pages and changing the number of rows per page.
  • Empty States: A "No results found" message when the data array is empty.
  • Responsive Design: A flex-based layout that adjusts pagination controls for mobile screens.
export function DataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
})

return (
<div className="flex flex-col gap-4">
<Table>
<TableHeader>
{/* Header rendering logic */}
</TableHeader>
<TableBody>
{/* Row rendering logic */}
</TableBody>
</Table>
{/* Pagination controls */}
</div>
)
}

To use the DataTable, you define columns using TanStack's ColumnDef and pass them along with your data, as seen in frontend/src/routes/_layout/items.tsx.