Reusable components, design conventions, and usage patterns for the KilomeTracker UI.
KilomeTracker uses a mobile-first design system built on shadcn/ui and Tailwind CSS v4. This page covers the custom components, semantic conventions, and layout patterns that every page should follow.
All imports use the @/ path alias. Use @/components/ui/..., @/contexts/..., and @/hooks/... — never relative paths.
StatCard (src/components/features/stats/StatCard.tsx) displays a single metric. The accent prop maps to a semantic domain color — always apply the correct accent for the type of data being shown.
Accent
Color
Use case
info
Blue
Fuel / combustible costs
warning
Amber
Maintenance / total cost
purple
Purple
Other expenses
success
Green
Active status / tax-deductible
(none)
Neutral
General totals, neutral metrics
import { StatCard } from "@/components/features/stats/StatCard";{/* Fuel cost → info */}<StatCard label="Fuel cost" value="Q 120.00" accent="info" />{/* Total cost → warning */}<StatCard label="Total cost" value="Q 850.00" accent="warning" />{/* Other expenses → purple */}<StatCard label="Other expenses" value="Q 200.00" accent="purple" />{/* Active vehicles → success */}<StatCard label="Active vehicles" value={12} accent="success" />{/* General total → no accent */}<StatCard label="Total" value="Q 1,170.00" />
These accents are backed by domain tokens in globals.css:
FilterPanel (src/components/ui/FilterPanel.tsx) is the standard filter UI for every history and list page. Never place filter fields inline on mobile.Behavior by breakpoint:
Mobile — renders a “Filters” button with an active-count badge. Tapping opens a Sheet sliding up from the bottom.
Desktop — renders a Card with a header (title + Clear button), a field grid, and an Apply button.
EmptyState (src/components/ui/empty-state.tsx) provides a consistent empty state UI across every page. Use it whenever a list or data view has no records to show.
import { EmptyState } from "@/components/ui/empty-state";<EmptyState icon={<Car className="h-12 w-12" />} title="No vehicles registered" description="Add your first vehicle to start tracking" action={{ label: "Add Vehicle", onClick: () => router.push("/add-vehicle") }}/>
The action prop accepts either onClick (for client-side navigation) or href (for a plain link).
CardSkeleton (src/components/ui/card-skeleton.tsx) is the standard loading state for any page that fetches data. Always show it while loading is true to prevent layout shift.
For the vehicle dashboard grid specifically, use VehicleCardSkeleton (src/components/ui/vehicle-card-skeleton.tsx) — it matches the exact layout of VehicleCard.
Touch target rule: all interactive elements must be at least 44×44 px. Action icon buttons in lists use h-8 w-8 (32 px) with sufficient padding from the parent container to meet the 44 px touch area.
These hooks provide global state from React Contexts mounted in the dashboard layout (src/app/(dashboard)/layout.tsx). Import them in any "use client" component within the dashboard.