# Creating Components

Build custom components for your foundation.

## Component Structure

Each section type lives in its own folder under `src/sections/`:

```
src/sections/
└── FeatureCard/
    ├── FeatureCard.jsx   # The React component (or index.jsx — both work)
    └── meta.js           # Content interface declaration
```

The `meta.js` file makes a component a section type — selectable via `type:` in content. Components without `meta.js` are ordinary React components that live wherever makes sense (typically `src/components/`).

---

## Minimal Component

### meta.js

```js
export default {
  title: 'Feature Card',
  family: 'features',
}
```

### index.jsx

```jsx
export default function FeatureCard({ content, params }) {
  const { title, paragraphs, images } = content

  return (
    <div className="p-6 border rounded-lg">
      {images[0] && <img src={images[0].src} alt={images[0].alt} className="w-16 h-16" />}
      <h3 className="text-xl font-bold mt-4">{title}</h3>
      {paragraphs[0] && <p className="text-gray-600 mt-2">{paragraphs[0]}</p>}
    </div>
  )
}
```

### Usage in content

```markdown
---
type: FeatureCard
---

![](/icons/rocket.svg)

# Fast Performance

Built for speed with optimized loading and caching.
```

---

## Component Props

Every section type receives:

```jsx
function MyComponent({ content, params, block }) {
  // content - Parsed markdown content
  // params  - Frontmatter parameters (with defaults from meta.js)
  // block   - Block instance (access to page, website)
}
```

### content

The runtime guarantees this structure:

```js
content = {
  // Headings
  title: '',           // H1
  pretitle: '',        // `#>` label line above the title
  subtitle: '',        // Heading after H1

  // Body
  paragraphs: [],      // Paragraph text
  links: [],           // [{ href, label }]
  lists: [],           // List items

  // Media
  images: [],            // [{ src, alt, title }]
  icons: [],           // Small images marked as icons

  // Structure
  items: [],           // Content groups from subsequent headings
  data: {},            // Tagged blocks + fetched data

  // Document order
  sequence: [],        // All elements in original order
}
```

**No null checks needed.** Empty arrays/strings are guaranteed.

### params

Configuration from frontmatter, with defaults from `meta.js`:

```markdown
---
type: Hero
theme: dark
layout: centered
---
```

```jsx
function Hero({ params, block }) {
  const { layout } = params
  // layout = 'centered' (from frontmatter)
  // Other params have defaults from meta.js
}
```

`theme: dark` is not among the params: it is one of the section's own settings — with `background`, `grid`, `vars` and `sticky` — which framework applies around your component. Here the runtime pins the section's dark context, and semantic tokens (`text-heading`) follow it. When a component needs the context for its own logic, it reads it with `useColorContext(block)`.

### block

Access to page and site context:

```jsx
function MyComponent({ block }) {
  const page = block.page
  const website = block.website

  console.log(page.title)
  console.log(website.name)
}
```

---

## Writing meta.js

The `meta.js` file defines your component's interface.

### Naming the Section Type

Every field is optional. These two tell an editor what the section type is:

```js
export default {
  title: 'Feature Card',        // Display name (inferred from the name when left out)
  family: 'features',           // The standard section type it belongs to, so an editor
                                // can show an illustration, an icon and a translated label
}
```

Run `uniweb families` for the ids, or see [Family](../reference/component-metadata.md#family).

### Describing Content

Document what content your component uses:

```js
export default {
  title: 'Hero',
  family: 'hero',

  content: {
    pretitle: 'Eyebrow text',
    title: 'Headline',
    subtitle: 'Secondary headline',
    paragraphs: 'Description [1-2]',    // [count] is guidance
    links: 'CTA buttons [1-2]',
    image: 'Hero image [1]',
  },
}
```

The count syntax (`[1]`, `[1-2]`, `[2+]`) documents expected quantity.

### Adding Parameters

Define configurable options:

```js
export default {
  title: 'Hero',
  family: 'hero',

  params: {
    variant: {
      type: 'select',
      label: 'Variant',
      options: ['plain', 'bold', 'gradient'],
      default: 'plain',
    },
    layout: {
      type: 'select',
      label: 'Layout',
      options: ['centered', 'left', 'split'],
      default: 'centered',
    },
    showPattern: {
      type: 'boolean',
      label: 'Show Background Pattern',
      default: false,
    },
  },
}
```

**Parameter types:**

| Type | UI | Value |
| --- | --- | --- |
| `select` | Dropdown | String from options |
| `boolean` | Toggle | true/false |
| `string` | Text input | Any string |
| `number` | Number input | Numeric value |

### Creating Presets

Named combinations of parameters, which an editor offers by `label` — picking one sets those params on the section:

```js
export default {
  title: 'Hero',
  family: 'hero',

  params: {
    variant: { type: 'select', options: ['plain', 'bold', 'gradient'], default: 'plain' },
    layout: { type: 'select', options: ['centered', 'left', 'split'], default: 'centered' },
  },

  presets: {
    default: {
      label: 'Plain Centered',
      params: { variant: 'plain', layout: 'centered' },
    },
    bold: {
      label: 'Bold Dark Hero',
      params: { variant: 'bold', layout: 'centered', theme: 'dark' },  // a preset may set the section's theme too
    },
    gradient: {
      label: 'Gradient Split',
      params: { variant: 'gradient', layout: 'split' },
    },
  },
}
```

In a file, write the params themselves. `uniweb add section Hero --starter --preset bold` prints starter content with that preset's params as its frontmatter:

```markdown
---
type: Hero
variant: bold
layout: centered
theme: dark
---
```

A `preset:` key in a section's frontmatter has no effect — the build warns and ignores it.

### Background Support

Enable background images/videos:

```js
export default {
  title: 'Hero',
  family: 'hero',
  background: true,    // Engine handles background rendering
}
```

Content authors can then configure:

```markdown
---
type: Hero
background:
  image: /images/hero-bg.jpg
  overlay:
    type: dark
    opacity: 0.5
---
```

---

## Handling Items

When content has multiple headings after the main content, they become `items`:

```markdown
---
type: Features
---

# Our Features

The best features for your needs.

## Fast

Lightning quick performance.

## Secure

Enterprise-grade security.

## Scalable

Grows with your needs.
```

The component receives:

```js
content.title = "Our Features"
content.paragraphs = ["The best features for your needs."]
content.items = [
  { title: "Fast", paragraphs: ["Lightning quick performance."] },
  { title: "Secure", paragraphs: ["Enterprise-grade security."] },
  { title: "Scalable", paragraphs: ["Grows with your needs."] },
]
```

Render items:

```jsx
function Features({ content }) {
  const { title, paragraphs, items } = content

  return (
    <section>
      <h2>{title}</h2>
      <p>{paragraphs[0]}</p>

      <div className="grid grid-cols-3 gap-6">
        {items.map((item, i) => (
          <div key={i}>
            <h3>{item.title}</h3>
            <p>{item.paragraphs[0]}</p>
          </div>
        ))}
      </div>
    </section>
  )
}
```

---

## Using Tagged Blocks

For structured data that doesn't fit markdown, use tagged data blocks:

````markdown
---
type: PricingTable
---

# Pricing

Choose the plan that's right for you.

```yaml:plans
- name: Starter
  price: 9
  features: [5 projects, 1GB storage]
- name: Pro
  price: 29
  features: [Unlimited projects, 10GB storage, Priority support]
```
````

Access in component:

```jsx
function PricingTable({ content }) {
  const { title, paragraphs } = content
  const plans = content.data.plans || []

  return (
    <section>
      <h2>{title}</h2>
      <p>{paragraphs[0]}</p>

      <div className="grid grid-cols-2 gap-6">
        {plans.map(plan => (
          <div key={plan.name}>
            <h3>{plan.name}</h3>
            <p className="text-3xl">${plan.price}/mo</p>
            <ul>
              {plan.features.map(f => <li key={f}>{f}</li>)}
            </ul>
          </div>
        ))}
      </div>
    </section>
  )
}
```

Define the schema in meta.js:

```js
export default {
  title: 'Pricing Table',
  family: 'pricing',

  data: {
    plans: {
      name: 'string',
      price: 'number',
      features: { type: 'array', items: { type: 'string' } },
    },
  },
}
```

---

## Accessing Context

Use hooks from `@uniweb/kit` to access site context — navigation, routing, theming, and locale:

```jsx
import { useWebsite, useActiveRoute } from '@uniweb/kit'

function Header({ content }) {
  const { website } = useWebsite()
  const { isActiveOrAncestor } = useActiveRoute()

  const pages = website.getPageHierarchy({ for: 'header' })

  return (
    <header>
      <span>{website.name}</span>
      <nav>
        {pages.map(page => (
          <a
            key={page.id}
            href={page.route}
            className={isActiveOrAncestor(page) ? 'active' : ''}
          >
            {page.label}
          </a>
        ))}
      </nav>
    </header>
  )
}
```

See [Kit Reference](../reference/kit-reference.md) for all available hooks.

---

## Building the Foundation

After creating components:

```bash
cd foundation
pnpm build
```

This generates:

- `dist/entry.js` — Bundled components
- `dist/meta/schema.json` — Component metadata for editors

---

## Generating Documentation

Generate `COMPONENTS.md` for content authors:

```bash
cd foundation
uniweb docs
```

This creates documentation with each component's:

- Description and family
- Content expectations
- Parameters and defaults
- Available presets

---

## Best Practices

1. **Graceful degradation** — Handle missing content without errors
2. **Sensible defaults** — Every param should have a good default
3. **Intent over implementation** — `variant: featured` not `backgroundColor: #1a1a1a`
4. **Minimal required content** — Components should render with minimal input
5. **Consistent naming** — Use the standard content element names

---

## See Also

- [Receiving Content](./receiving-content.md) — Consuming the parsed structure well: guarantees, multi-line slots, items, the two views
- [Component Metadata](../reference/component-metadata.md) — Full meta.js reference
- [Content Structure](../reference/content-structure.md) — How content is parsed
- [Kit Reference](../reference/kit-reference.md) — Hooks, components, and utilities
- [Foundation Configuration](../reference/foundation-config.md) — CSS variables and Layout