UI Components & Design System
The frontend uses shadcn/ui primitives (copy-in components built on Radix UI and Tailwind CSS) composed into higher-level section components that map one-to-one with CMS content types. Understanding this two-layer structure is the key to customizing the site without fighting the design system.
Three layers of components
components/
├── ui/ ← primitives : accordion, button, card, input, …
├── sections/ ← page blocks : HeroSection, PricingSection, FAQSection, …
├── layout/ ← chrome : Header, Footer
└── shared/ ← cross-cutting: BookingCTA, Analytics, CookieConsent, …
ui/: atomic, unopinionated primitives. Owned by your repo (not a node_modules dependency), so you edit them freely.sections/: full-width page blocks that consume Directus data and render with the primitives.layout/+shared/: persistent chrome (header, footer) and cross-page concerns (analytics, cookie banner, booking CTA).
UI primitives
These ship pre-wired. Each is a thin shadcn/ui component you can extend or restyle.
| Component | File | Use for |
|---|---|---|
Accordion | ui/accordion.tsx | FAQ, disclosure |
Badge | ui/badge.tsx | Tags, status pills |
Button | ui/button.tsx | CTAs, form submits |
Card | ui/card.tsx | Services, pricing tiers, testimonials |
Input / Textarea | ui/input.tsx, ui/textarea.tsx | Contact form |
Section | ui/section.tsx | Container + vertical rhythm wrapper used by every section |
Separator | ui/separator.tsx | Visual dividers |
Tabs | ui/tabs.tsx | Grouped content surfaces |
Primitives follow the shadcn/ui pattern: variants through class-variance-authority, styling through Tailwind utilities. Add a new primitive with npx shadcn@latest add <component>: see the official shadcn/ui docs for the full catalog.
Section components
The marketing page is a stack of section blocks. Each one reads from a Directus collection (or falls back to copy in config/ui/) and renders with shared spacing tokens.
| Section | File | Backed by |
|---|---|---|
| Hero | sections/HeroSection.tsx | pages → home with a hero page_section |
| Features | sections/FeaturesSection.tsx | page_sections of type: features |
| Services | sections/ServicesSection.tsx | services collection |
| Testimonials | sections/TestimonialsSection.tsx | testimonials collection |
| Pricing | sections/PricingSection.tsx | pricing_plans collection |
| FAQ | sections/FAQSection.tsx | faqs collection |
| CTA | sections/CTASection.tsx | page_sections of type: cta |
| Process | sections/ProcessSection.tsx | page_sections of type: process |
| Readiness | sections/ReadinessSection.tsx | page_sections of type: readiness |
All sections are server components by default: they fetch data server-side and stream HTML. Interactive bits (form inputs, accordions, theme toggle) are client components marked with "use client".
Layout chrome
layout/Header.tsx: top nav, built from thenavigationcollection, locale switcher included.layout/Footer.tsx: grouped links fromfooter_groups+footer_links, plussite_settingscontact info and JSON-LD.
The root layout at app/layout.tsx wires fonts, global CSS, analytics, and the cookie consent banner.
Shared cross-page components
shared/BookingCTA.tsx: floating booking button tied toNEXT_PUBLIC_BOOKING_URL(Calendly / Cal.com).shared/ChatWidget.tsx: placeholder slot for a chat tool (Crisp, Intercom, …).shared/ClientLogos.tsx: logo strip for social proof.shared/CookieConsent.tsx: GDPR-friendly banner.shared/ExitIntent.tsx: optional exit-intent modal.shared/GoogleAnalytics.tsx/GoogleTagManager.tsx: gated onNEXT_PUBLIC_GA_ID/NEXT_PUBLIC_GTM_ID.shared/LeadMagnet.tsx: slot-in component for lead capture.shared/RichContentPage.tsx: renders blog/docs/legal rich-text bodies.shared/ThemeToggle.tsx: light/dark switch (usesnext-themes).
Tailwind and design tokens
Global styles live in frontend/src/styles/ and the design tokens are in tailwind.config.ts. Dark mode uses the class strategy, toggled by the theme toggle.
Rebrand checklist
- Brand constants: edit
config/brand.ts(name, description, contact, social, trust metrics). - Logo + favicon: replace the assets in
frontend/public/. - Colors: edit the CSS variables in
src/styles/globals.css(ortheme.extend.colorsintailwind.config.tsif you prefer the config-based path). - Typography: swap the font import in the root layout and update
fontFamilyintailwind.config.ts. - Copy: edit in Directus, not in code. Static fallbacks in
config/ui/update the "Directus is offline" view.
Adding a new section
- Create
components/sections/YourSection.tsxas a server component. - Add a fetcher in
lib/directus.tsif it reads from Directus. - Import it into the page that renders it (e.g.
app/[locale]/page.tsx). - Add fallback copy in
config/ui/pages.tsso it degrades gracefully if the CMS is empty.
Accessibility and i18n
- shadcn/ui primitives are built on Radix UI, which gives keyboard nav, focus management, and ARIA attributes for free: see the Radix UI docs.
- All visible strings either come from Directus (per-row translations) or from
next-intlJSON messages undersrc/messages/(UI strings). Both are locale-aware.
Next steps
- Working with Directus: add fields or collections, then render them in a section.
- Frontend Setup: envs and dev-server config.
- Content Management: edit section copy without code.