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.

ComponentFileUse for
Accordionui/accordion.tsxFAQ, disclosure
Badgeui/badge.tsxTags, status pills
Buttonui/button.tsxCTAs, form submits
Cardui/card.tsxServices, pricing tiers, testimonials
Input / Textareaui/input.tsx, ui/textarea.tsxContact form
Sectionui/section.tsxContainer + vertical rhythm wrapper used by every section
Separatorui/separator.tsxVisual dividers
Tabsui/tabs.tsxGrouped 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.

SectionFileBacked by
Herosections/HeroSection.tsxpageshome with a hero page_section
Featuressections/FeaturesSection.tsxpage_sections of type: features
Servicessections/ServicesSection.tsxservices collection
Testimonialssections/TestimonialsSection.tsxtestimonials collection
Pricingsections/PricingSection.tsxpricing_plans collection
FAQsections/FAQSection.tsxfaqs collection
CTAsections/CTASection.tsxpage_sections of type: cta
Processsections/ProcessSection.tsxpage_sections of type: process
Readinesssections/ReadinessSection.tsxpage_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 the navigation collection, locale switcher included.
  • layout/Footer.tsx: grouped links from footer_groups + footer_links, plus site_settings contact 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 to NEXT_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 on NEXT_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 (uses next-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

  1. Brand constants: edit config/brand.ts (name, description, contact, social, trust metrics).
  2. Logo + favicon: replace the assets in frontend/public/.
  3. Colors: edit the CSS variables in src/styles/globals.css (or theme.extend.colors in tailwind.config.ts if you prefer the config-based path).
  4. Typography: swap the font import in the root layout and update fontFamily in tailwind.config.ts.
  5. Copy: edit in Directus, not in code. Static fallbacks in config/ui/ update the "Directus is offline" view.

Adding a new section

  1. Create components/sections/YourSection.tsx as a server component.
  2. Add a fetcher in lib/directus.ts if it reads from Directus.
  3. Import it into the page that renders it (e.g. app/[locale]/page.tsx).
  4. Add fallback copy in config/ui/pages.ts so 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-intl JSON messages under src/messages/ (UI strings). Both are locale-aware.

Next steps