# Templates
Templates are complete website starting points with customizable theming, pre-built sections, and consistent patterns.
---
## Directory Structure
```
src/
├── templates/ # Template Pages (full websites)
│ ├── hotel/
│ │ ├── page.tsx # Page component with sections
│ │ └── theme.css # Colors, buttons, cards
│ ├── saas/
│ └── ...
│
└── components/
└── templates/ # Template Components (reusable sections)
├── ResultsComparison.tsx
└── HeroOverlayMarquee.tsx
```
---
## Template Pages
Located in `/src/templates/[name]/`, each template contains:
- `page.tsx` - Full page with StyleProvider wrapper and sections
- `theme.css` - CSS variables for colors, typography, buttons, cards
### page.tsx Structure
```tsx
import { StyleProvider } from "@/components/ui/StyleProvider";
import NavbarInline from "@/components/ui/NavbarInline";
import HeroBillboard from "@/components/sections/hero/HeroBillboard";
import FooterSimple from "@/components/sections/footer/FooterSimple";
import "./theme.css";
export default function TemplateName() {
return (
{/* More sections... */}
);
}
```
### StyleProvider Options
| Prop | Options |
|------|---------|
| `siteBackground` | `"aurora"`, `"cornerGlow"`, `"lightRaysCenter"`, `"lightRaysCorner"`, `"floatingGradient"`, `"gradientBars"`, `"horizonGlow"`, `"none"` |
| `heroBackground` | `"cornerGlow"`, `"lightRaysCenter"`, `"lightRaysCorner"`, `"horizonGlow"`, `"gradientBars"`, `"none"` |
| `buttonVariant` | `"stagger"`, `"arrow"`, `"expand"`, `"elastic"`, `"shift"`, `"magnetic"`, `"default"` |
### Asset Handling
Use Google Cloud Storage CDN for all media:
```
https://storage.googleapis.com/webild/default/templates/[template-name]/[asset].webp
https://storage.googleapis.com/webild/default/templates/[template-name]/[asset].mp4
```
---
## Adding Sections to a Template
### Decision Flow
```
1. Need a section for your template
↓
2. Check /src/components/sections/[category]/
↓
┌───────┴───────┐
↓ ↓
EXISTS? DOESN'T EXIST?
↓ ↓
Import & Create template component
use it in /src/components/templates/
```
### Step 1: Check Existing Sections
Look in `/src/components/sections/` by category:
| Category | Path | Examples |
|----------|------|----------|
| Hero | `hero/` | HeroBillboard, HeroSplit, HeroOverlay |
| Features | `features/` | FeaturesBento, FeaturesMediaCards |
| Testimonial | `testimonial/` | TestimonialTrustCard, TestimonialRatingCards |
| Pricing | `pricing/` | PricingCards, PricingComparison |
| Footer | `footer/` | FooterSimple, FooterSimpleCard |
| Contact | `contact/` | ContactSplitForm, ContactCenter |
| FAQ | `faq/` | FaqSimple, FaqTwoColumn |
| Team | `team/` | TeamCards, TeamGrid |
| Blog | `blog/` | BlogSimpleCards |
| About | `about/` | AboutTextSplit |
| Metrics | `metrics/` | MetricsCards |
### Step 2a: If Section Exists → Use It
```tsx
import HeroBillboard from "@/components/sections/hero/HeroBillboard";
import FeaturesMediaCards from "@/components/sections/features/FeaturesMediaCards";
```
### Step 2b: If Section Doesn't Exist → Create Template Component
1. **First, study similar sections** in `/src/components/sections/[category]/`
- Read 2-3 sections in the same category
- Understand their props structure
- Note which UI components they use
- Observe animation and layout patterns
2. **Create your component** in `/src/components/templates/[Name].tsx`
- Follow the same patterns you observed
- Use consistent prop naming (tag, title, description, items)
- Import the same UI components (Button, TextAnimation, etc.)
---
## Template Components
Located in `/src/components/templates/`, these are reusable section-level components for templates when no standard section fits.
### When to Create
Create when no suitable section exists in `/src/components/sections/` (see "Adding Sections" above).
### Current Components
| Component | Purpose |
|-----------|---------|
| `ResultsComparison` | Before/after comparison marquee with treatment cards |
| `HeroOverlayMarquee` | Full-screen hero with media background and bottom marquee |
### Structure
Template components follow the same patterns as UI components:
```tsx
import Button from "@/components/ui/Button";
import TextAnimation from "@/components/ui/TextAnimation";
import ImageOrVideo from "@/components/ui/ImageOrVideo";
import ScrollReveal from "@/components/ui/ScrollReveal";
import { cls } from "@/lib/utils";
type ItemType = {
// item properties
};
interface ComponentProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: ItemType[];
}
const ComponentName = ({ tag, title, description, primaryButton, secondaryButton, items }: ComponentProps) => {
return (
);
};
export default ComponentName;
```
---
## theme.css
Each template's theme.css defines the visual identity.
### Required Structure
```css
/* [Template Name] - [Theme Description] */
@import "tailwindcss";
@import "../../styles/masks.css";
@import "../../styles/animations.css";
:root {
/* Colors (9 required) */
--background: #ffffff;
--card: #f5f5f5;
--foreground: #171717;
--primary-cta: #171717;
--primary-cta-text: #ffffff;
--secondary-cta: #f5f5f5;
--secondary-cta-text: #171717;
--accent: #171717;
--background-accent: #171717;
/* Layout */
--radius: 1.5rem;
--width-content-width: clamp(40rem, 72.5vw, 100rem);
/* Carousel */
--vw-1_5: 1.35rem;
--width-carousel-padding: calc((100vw - var(--width-content-width)) / 2 + 1px - 1rem);
--width-carousel-padding-controls: calc((100vw - var(--width-content-width)) / 2 + 1px);
--width-carousel-item-2: calc(var(--width-content-width) / 2 - var(--vw-1_5) / 2);
--width-carousel-item-3: calc(var(--width-content-width) / 3 - var(--vw-1_5) / 3 * 2);
--width-carousel-item-4: calc(var(--width-content-width) / 4 - var(--vw-1_5) / 4 * 3);
/* Typography */
--text-2xs: 0.62rem;
--text-xs: 0.72rem;
--text-sm: 0.82rem;
--text-base: 0.92rem;
--text-lg: 1rem;
--text-xl: 1.1rem;
--text-2xl: 1.3rem;
--text-3xl: 1.6rem;
--text-4xl: 2rem;
--text-5xl: 2.75rem;
--text-6xl: 3.3rem;
--text-7xl: 4rem;
--text-8xl: 4.5rem;
--text-9xl: 7rem;
}
/* Mobile typography */
@media (max-width: 768px) {
:root {
--text-2xs: 2.5vw;
/* ... mobile sizes ... */
--width-content-width: 85vw;
}
}
@theme inline {
/* Tailwind color mappings */
--color-background: var(--background);
--color-card: var(--card);
--color-foreground: var(--foreground);
/* ... */
}
/* Base styles */
* { scrollbar-width: thin; }
html { overscroll-behavior: none; }
body { background-color: var(--background); color: var(--foreground); }
/* WEBILD_CARD_STYLE */
.card {
background: var(--color-card);
border: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
}
/* WEBILD_PRIMARY_BUTTON */
.primary-button {
background: linear-gradient(180deg, #1e1e1e 0%, #0c0c0c 100%);
box-shadow: /* complex shadow */;
}
/* WEBILD_SECONDARY_BUTTON */
.secondary-button {
background: var(--color-secondary-cta);
border: 1px solid rgba(0, 0, 0, 0.08);
}
```
---
## Props & Naming Conventions
### Standard Props
| Prop | Usage |
|------|-------|
| `tag` | Small badge/label text |
| `title` | Main heading (h1/h2) |
| `description` | Subtitle/description text |
| `primaryButton` | `{ text: string; href: string }` |
| `secondaryButton` | `{ text: string; href: string }` |
| `items` / `features` | Array of data items |
| `imageSrc` / `videoSrc` | Media sources (exclusive) |
| `className` | Main wrapper element |
| `[element]ClassName` | Specific element (titleClassName, etc.) |
### Discriminated Union for Media
When a component accepts either image OR video (not both):
```typescript
type Props = {
tag: string;
title: string;
// ...other props
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
```
---
## Code Patterns
### cls() Utility
Always use for conditional classNames:
```tsx
import { cls } from "@/lib/utils";
className={cls(
"base-classes px-4 py-2",
isActive && "bg-primary",
variant === "large" ? "text-xl" : "text-base"
)}
```
### Responsive Padding
Use consistent responsive spacing:
```tsx
// Container padding
className="p-3 xl:p-4 2xl:p-5"
// Gaps
className="gap-3 xl:gap-4 2xl:gap-5"
// Margins
className="mb-3 xl:mb-4 2xl:mb-5"
```
### UI Components
Import from `/src/components/ui/`:
| Component | Purpose |
|-----------|---------|
| `Button` | CTA buttons with variant prop |
| `TextAnimation` | Animated headings with gradientText option |
| `ImageOrVideo` | Handles both img and video display |
| `ScrollReveal` | Scroll-triggered animation wrapper |
| `AvatarGroup` | Avatar displays with overflow and label |
| `Accordion` | Expandable content sections |
### Marquee Pattern
For infinite horizontal scrolling:
```tsx
const duplicated = [...items, ...items, ...items, ...items];
{duplicated.map((item, i) => (
{/* Item content */}
))}
```
### Section Header Pattern
Consistent header for sections:
```tsx
{(primaryButton || secondaryButton) && (
{primaryButton && }
{secondaryButton && }
)}
```
---
## Template Catalog
| Template | Theme | Navbar | siteBackground |
|----------|-------|--------|----------------|
| landscaping | Light Green | NavbarCentered | aurora |
| luxury-travel-agency | Light Beige | NavbarInline | cornerGlow |
| hvac | Light Blue | NavbarInline | lightRaysCenter |
| plumber | Dark Blue | NavbarInline | lightRaysCorner |
| roofing | Dark Orange | NavbarCentered | cornerGlow |
| saas | Dark Purple | NavbarInline | aurora |
| skincare | Light Sand | NavbarFloating | floatingGradient |
| dentist | Light Blue | NavbarInline | lightRaysCenter |
| detailing | Dark Orange | NavbarCentered | cornerGlow |
| real-estate | Light Elegant | NavbarFloating | gradientBars |
| skincare-luxury | Light Rose | NavbarDropdown | floatingGradient |
| med-spa | Light Mauve | NavbarCentered | horizonGlow |
| hotel | Dark Luxury | NavbarInline | none |
---
## Creating a Template
1. Create `/src/templates/[name]/page.tsx` and `theme.css`
2. Set up StyleProvider with appropriate background/button variants
3. Import sections from `/src/components/sections/` or template components
4. Configure theme.css with colors, buttons, and card styles
5. Use CDN URLs for all media assets
6. Add route in App.tsx