Components overview
All reusable UI components themed via CSS variables. No hardcoded colors — swap the palette in globals.css without touching a single component.
Design system
Every component in NextForge follows these rules:
- Colors only via CSS variables (var(--color-*)) — never hardcoded hex values
- Mobile-first Tailwind classes — base styles, then sm:, md:, lg: overrides
- Accessible by default — proper aria attributes, focus-visible rings, role attributes
- TypeScript props — all components have fully typed prop interfaces
- No external UI library — components are self-contained and fully owned by you
Theming via CSS variables
The entire color palette is defined in app/globals.css in the @theme block. To change the primary color across every component, change one variable:
app/globals.css
@theme {
--color-primary: #2563eb; /* change this */
--color-primary-hover: #1d4ed8; /* and this */
--color-primary-foreground: #ffffff;
}Every Button, Input focus ring, active nav link, and toast accent updates automatically.
Component list
| Component | Location | Type | Description |
|---|---|---|---|
| Button | components/ui/Button.tsx | Client | 5 variants, isLoading, auto-disabled |
| Input | components/ui/Input.tsx | Client | Label, error, hint, icon slots |
| Loader | components/ui/Loader.tsx | Client | Spinner, dots, skeleton variants |
| Toast | components/ui/Toast.tsx | Client | 4 types, auto-dismiss, slide-up animation |
| Modal | components/ui/Modal.tsx | Client | Portal, focus trap, ESC close |
| Navbar | components/nav/Navbar.tsx | Client | Auth-aware, mobile hamburger, dark mode |
| Footer | components/footer/Footer.tsx | Server | Dynamic link groups |
Loading states convention
All form submissions in NextForge use React's useTransition:
const [isPending, startTransition] = useTransition()
function handleSubmit(formData: FormData) {
startTransition(async () => {
const result = await someServerAction(formData)
// handle result
})
}
// Button is disabled + shows spinner for the full duration:
<Button isLoading={isPending} disabled={isPending}>
Submit
</Button>This prevents double-submission, gives immediate visual feedback, and keeps the UI responsive during server round-trips.