> ## Documentation Index
> Fetch the complete documentation index at: https://docs.primo.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Writing Components

> Build reusable components and turn them into content blocks.

Components are the foundation of Primo. You write components using Svelte, attach content fields to them, and they become blocks that editors can use to build pages. This guide covers everything you need to know about writing effective components.

## Components vs Blocks

Understanding the distinction helps you build better:

**Components** are pure Svelte code:

* Reusable UI elements (Button, Card, Hero)
* Accept props and handle logic
* No content fields attached
* Can be used anywhere in your Svelte code

**Blocks** are components with content fields:

* Same Svelte component code
* Plus editable content fields (text, images, links)
* Used specifically in page types and pages
* Editors can modify content, not code

<Info>
  When you create a block in Primo, you're writing a Svelte component and then attaching fields to it. The component code stays the same; fields make it editable.
</Info>

## Component Structure

Every Primo component follows this structure:

```svelte theme={null}
<script>
  // 1. Import statements (if needed)
  // 2. Props (content fields)
  // 3. Reactive logic
</script>

<!-- 4. HTML markup -->

<style>
  /* 5. Scoped styles */
</style>
```

## Writing Your First Component

Let's build a simple Button component step by step:

<Steps>
  <Step title="Identify the fields you need">
    Think about what data your component needs. For a button block:

    * `text`: The button text
    * `href`: The link destination
    * `variant`: The button style (primary, secondary, outline)

    Fields will be automatically available in your component code.

    <Tip>
      Plan your fields first, then reference them in your template. You can set default values in the field configuration.
    </Tip>
  </Step>

  <Step title="Add the markup">
    Write the HTML structure:

    ```svelte theme={null}
    <a href={href} class="button" class:primary={variant === "primary"} class:secondary={variant === "secondary"} class:outline={variant === "outline"}>
      {text}
    </a>
    ```
  </Step>

  <Step title="Style the component">
    Add scoped styles:

    ```svelte theme={null}
    <style>
      .button {
        display: inline-block;
        padding: 0.75rem 1.5rem;
        border-radius: 0.5rem;
        text-decoration: none;
        font-weight: 600;
        transition: all 0.2s;
      }
      
      .primary {
        background: #667eea;
        color: white;
      }
      
      .secondary {
        background: #764ba2;
        color: white;
      }
      
      .outline {
        border: 2px solid #667eea;
        color: #667eea;
        background: transparent;
      }
      
      .button:hover {
        transform: translateY(-2px);
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
      }
    </style>
    ```
  </Step>
</Steps>

## Best Practices

### 1. Handle Empty Values

Components should work gracefully even with empty fields:

```svelte theme={null}
<!-- Good: Provides fallback values -->
<h1>{headline || "Welcome"}</h1>
<p>{subheadline || "Default description"}</p>

<!-- Also good: Conditional rendering -->
{#if headline}
  <h1>{headline}</h1>
{/if}

{#if subheadline}
  <p>{subheadline}</p>
{/if}
```

### 2. Use Conditional Rendering

Only show elements when content exists:

```svelte theme={null}
{#if image_url}
  <figure>
    <img src={image_url} alt={alt_text} />
    {#if caption}
      <figcaption>{caption}</figcaption>
    {/if}
  </figure>
{/if}
```

### 3. Handle Arrays Safely

When working with repeater fields, always check if the array has items:

```svelte theme={null}
{#if items && items.length > 0}
  <ul>
    {#each items as item}
      <li>{item.name}</li>
    {/each}
  </ul>
{:else}
  <p>No items to display</p>
{/if}
```

### 4. Use Scoped Styles

Primo components use scoped styles by default. Use `:global()` when you need to style child elements:

```svelte theme={null}
<style>
  .content :global(h2) {
    font-size: 2rem;
    margin-bottom: 1rem;
  }
  
  .content :global(p) {
    line-height: 1.8;
  }
</style>
```

### 5. Make Components Responsive

Always consider mobile devices:

```svelte theme={null}
<style>
  .grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 2rem;
  }

  @media (max-width: 768px) {
    .grid {
      grid-template-columns: 1fr;
      gap: 1rem;
    }
  }
</style>
```

### 6. Create Component Variations

Instead of creating many similar blocks, use fields to create variations of a single component:

**Layout variations:**

```svelte theme={null}
<!-- Field: image_position (Select: "left", "right") -->
<div class="media-block" class:image-right={image_position === "right"}>
  <div class="image">
    {#if image?.url}
      <img src={image.url} alt={image.alt} />
    {/if}
  </div>
  <div class="content">
    <h2>{headline}</h2>
    <p>{description}</p>
  </div>
</div>

<style>
  .media-block {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 2rem;
  }

  .media-block.image-right {
    direction: rtl;
  }

  .media-block.image-right > * {
    direction: ltr;
  }
</style>
```

**Color/theme variations:**

```svelte theme={null}
<!-- Field: variant (Select: "light", "dark", "primary") -->
<section class="hero variant-{variant}">
  <h1>{headline}</h1>
  <p>{description}</p>
</section>

<style>
  .hero {
    padding: 4rem 2rem;
    text-align: center;
  }

  .variant-light {
    background: #f5f5f5;
    color: #1a1a1a;
  }

  .variant-dark {
    background: #1a1a1a;
    color: #ffffff;
  }

  .variant-primary {
    background: var(--primary-color);
    color: #ffffff;
  }
</style>
```

**Size variations:**

```svelte theme={null}
<!-- Field: size (Select: "small", "medium", "large") -->
<button class="btn btn-{size}">
  {label}
</button>

<style>
  .btn {
    padding: 0.5rem 1rem;
    font-size: 1rem;
  }

  .btn-small {
    padding: 0.25rem 0.75rem;
    font-size: 0.875rem;
  }

  .btn-large {
    padding: 0.75rem 1.5rem;
    font-size: 1.25rem;
  }
</style>
```

<Tip>
  One flexible block with variant options is easier to maintain than multiple similar blocks. Editors can adjust layouts without developers creating new components.
</Tip>

## Working with Rich Content

### Rich Text Fields

When using rich text fields, use `{@html}` to render HTML:

```svelte theme={null}
<div class="rich-text">
  {@html content}
</div>

<style>
  .rich-text :global(h2) {
    font-size: 2rem;
    margin-top: 2rem;
    margin-bottom: 1rem;
  }

  .rich-text :global(p) {
    margin-bottom: 1.5rem;
    line-height: 1.8;
  }

  .rich-text :global(ul), .rich-text :global(ol) {
    margin-bottom: 1.5rem;
    padding-left: 2rem;
  }
</style>
```

<Warning>
  Only use `{@html}` with trusted content. Primo sanitizes rich text fields, but be cautious with user-generated content.
</Warning>

### Markdown Content

If you're using markdown fields, Primo processes them automatically. But you can also use a markdown processor for additional control:

```svelte theme={null}
<script>
  import { marked } from 'marked'; // If you add this dependency

  $: html_content = marked(markdown_content || "");
</script>

<div class="markdown-content">
  {@html html_content}
</div>
```

## Advanced Patterns

### Reactive Statements

Use reactive statements (`$:`) for computed values based on fields:

```svelte theme={null}
<script>
  $: displayed_items = (items || []).slice(0, max_items);
  $: has_more = (items || []).length > max_items;
</script>

{#each displayed_items as item}
  <div>{item.name}</div>
{/each}

{#if has_more}
  <p>And {items.length - max_items} more...</p>
{/if}
```

### Event Handling

Handle user interactions:

```svelte theme={null}
<script>
  function handleClick() {
    // Analytics, tracking, etc.
    console.log('CTA clicked');
  }
</script>

<a href={cta_link} on:click={handleClick} class="cta">
  {cta_text}
</a>
```

### Conditional Classes

Use dynamic classes for variants:

```svelte theme={null}
<div class="card" class:primary={variant === "primary"} class:large={size === "large"}>
  <!-- content -->
</div>

<style>
  .card.primary {
    background: #667eea;
    color: white;
  }

  .card.large {
    padding: 2rem;
  }
</style>
```

## Component Composition

Build complex components from simpler ones:

```svelte theme={null}
<script>
  import Button from './Button.svelte';
</script>

<section class="hero">
  <h1>{headline}</h1>
  <p>{description}</p>
  <Button text={cta_text} href={cta_link} variant="primary" />
</section>
```

<Info>
  In Primo's component editor, you write single-file components. For composition, you can create separate blocks and reference them, or write everything in one component file.
</Info>

## Performance Considerations

### Lazy Loading Images

For image-heavy components:

```svelte theme={null}
<img src={image_url} alt={alt_text} loading="lazy" />
```

### Optimize Re-renders

Use reactive statements efficiently:

```svelte theme={null}
<script>
  // Good: Only recalculates when items change
  $: sorted_items = [...(items || [])].sort((a, b) => a.name.localeCompare(b.name));
</script>

{#each sorted_items as item}
  <div>{item.name}</div>
{/each}
```

## Testing Your Components

### Use the Preview

Primo's in-browser editor provides instant preview:

1. Write your component code
2. Add test content in fields
3. See changes update in real-time

<Tip>
  Test with empty fields, long text, special characters, and edge cases to ensure your component is robust.
</Tip>

### Test Different Screen Sizes

Use browser dev tools in the preview to test:

* Mobile (320px - 768px)
* Tablet (768px - 1024px)
* Desktop (1024px+)

## Common Patterns

<AccordionGroup>
  <Accordion title="Card Component">
    ### Card Component

    ```svelte theme={null}
    <article class="card">
      {#if image_url}
        <img src={image_url} alt={title} />
      {/if}
      <div class="card-content">
        <h3>{title}</h3>
        {#if description}
          <p>{description}</p>
        {/if}
        {#if link}
          <a href={link} class="card-link">Read More →</a>
        {/if}
      </div>
    </article>

    <style>
      .card {
        border: 1px solid #e2e8f0;
        border-radius: 0.5rem;
        overflow: hidden;
      }

      .card-content {
        padding: 1.5rem;
      }
    </style>
    ```
  </Accordion>

  <Accordion title="Grid Layout">
    ### Grid Layout

    ```svelte theme={null}
    <section class="grid" style="--columns: {columns}">
      {#each items as item}
        <div class="grid-item">{item.content}</div>
      {/each}
    </section>

    <style>
      .grid {
        display: grid;
        grid-template-columns: repeat(var(--columns), 1fr);
        gap: 2rem;
      }

      @media (max-width: 768px) {
        .grid {
          grid-template-columns: 1fr;
        }
      }
    </style>
    ```
  </Accordion>

  <Accordion title="Accordion Component">
    ### Accordion Component

    ```svelte theme={null}
    <script>
      export let items = [];
      
      let open_index = null;
      
      function toggle(index) {
        open_index = open_index === index ? null : index;
      }
    </script>

    <div class="accordion">
      {#each items as item, index}
        <div class="accordion-item">
          <button class="accordion-header" on:click={() => toggle(index)}>
            {item.title}
            <span class="icon">{open_index === index ? '−' : '+'}</span>
          </button>
          {#if open_index === index}
            <div class="accordion-content">
              {item.content}
            </div>
          {/if}
        </div>
      {/each}
    </div>
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Working with Fields" icon="pen-to-square" href="/building-sites/working-with-fields">
    Learn how to attach content fields to your components.
  </Card>

  <Card title="Using the Component Library" icon="layer-group" href="/building-sites/using-the-component-library">
    Organize and reuse components across sites.
  </Card>

  <Card title="Field Types Reference" icon="book" href="/reference/field-types">
    Complete reference of all available field types.
  </Card>

  <Card title="Your First Site" icon="rocket" href="/building-sites/your-first-site">
    See components in action with a complete site example.
  </Card>
</CardGroup>

<Tip>
  The best components are simple, focused, and reusable. Start with basic functionality, then add complexity only when needed.
</Tip>
