---
title: Widget Areas
description: Add dynamic content blocks to sidebars, footers, and other template regions.
---

import { Aside, Steps, Tabs, TabItem } from "@astrojs/starlight/components";

Widget areas are named regions in your templates where administrators can place content blocks. Use them for sidebars, footer columns, promotional banners, or any section that editors should control without touching code.

## Querying Widget Areas

Use `getWidgetArea()` to fetch a widget area by name:

```astro title="src/layouts/Base.astro"
---
import { getWidgetArea } from "emdash";

const sidebar = await getWidgetArea("sidebar");
---

{sidebar && sidebar.widgets.length > 0 && (
  <aside class="sidebar">
    {sidebar.widgets.map(widget => (
      <div class="widget">
        {widget.title && <h3>{widget.title}</h3>}
        <!-- Render widget content -->
      </div>
    ))}
  </aside>
)}
```

The function returns `null` if the widget area does not exist.

## Widget Area Structure

A widget area contains metadata and an array of widgets:

```ts
interface WidgetArea {
	id: string;
	name: string; // Unique identifier ("sidebar", "footer-1")
	label: string; // Display name ("Main Sidebar")
	description?: string;
	widgets: Widget[];
}

interface Widget {
	id: string;
	type: "content" | "menu" | "component";
	title?: string;
	// Type-specific fields
	content?: PortableTextBlock[]; // For content widgets
	menuName?: string; // For menu widgets
	componentId?: string; // For component widgets
	componentProps?: Record<string, unknown>;
}
```

## Widget Types

EmDash supports three widget types:

### Content Widgets

Rich text content stored as Portable Text. Render using the `PortableText` component:

```astro
---
import { PortableText } from "emdash/ui";
---

{widget.type === "content" && widget.content && (
  <div class="widget-content">
    <PortableText value={widget.content} />
  </div>
)}
```

### Menu Widgets

Display a navigation menu within a widget area:

```astro
---
import { getMenu } from "emdash";

const menu = widget.menuName ? await getMenu(widget.menuName) : null;
---

{widget.type === "menu" && menu && (
  <nav class="widget-nav">
    <ul>
      {menu.items.map(item => (
        <li><a href={item.url}>{item.label}</a></li>
      ))}
    </ul>
  </nav>
)}
```

### Component Widgets

Render a registered component with configurable props. EmDash includes these core components:

| Component ID        | Description             | Props                                 |
| ------------------- | ----------------------- | ------------------------------------- |
| `core:recent-posts` | List of recent posts    | `count`, `showThumbnails`, `showDate` |
| `core:categories`   | Category list           | `showCount`, `hierarchical`           |
| `core:tags`         | Tag cloud               | `showCount`, `limit`                  |
| `core:search`       | Search form             | `placeholder`                         |
| `core:archives`     | Monthly/yearly archives | `type`, `limit`                       |

## Rendering Widgets

Create a reusable widget renderer component:

```astro title="src/components/WidgetRenderer.astro"
---
import { PortableText } from "emdash/ui";
import { getMenu } from "emdash";
import type { Widget } from "emdash";

// Import your widget components
import RecentPosts from "./widgets/RecentPosts.astro";
import Categories from "./widgets/Categories.astro";
import TagCloud from "./widgets/TagCloud.astro";
import SearchForm from "./widgets/SearchForm.astro";
import Archives from "./widgets/Archives.astro";

interface Props {
  widget: Widget;
}

const { widget } = Astro.props;

const componentMap: Record<string, any> = {
  "core:recent-posts": RecentPosts,
  "core:categories": Categories,
  "core:tags": TagCloud,
  "core:search": SearchForm,
  "core:archives": Archives,
};

const menu = widget.type === "menu" && widget.menuName
  ? await getMenu(widget.menuName)
  : null;
---

<div class="widget">
  {widget.title && <h3 class="widget-title">{widget.title}</h3>}

  {widget.type === "content" && widget.content && (
    <div class="widget-content">
      <PortableText value={widget.content} />
    </div>
  )}

  {widget.type === "menu" && menu && (
    <nav class="widget-menu">
      <ul>
        {menu.items.map(item => (
          <li><a href={item.url}>{item.label}</a></li>
        ))}
      </ul>
    </nav>
  )}

  {widget.type === "component" && widget.componentId && componentMap[widget.componentId] && (
    <Fragment>
      {(() => {
        const Component = componentMap[widget.componentId!];
        return <Component {...widget.componentProps} />;
      })()}
    </Fragment>
  )}
</div>
```

## Example Widget Components

### Recent Posts Widget

```astro title="src/components/widgets/RecentPosts.astro"
---
import { getEmDashCollection } from "emdash";

interface Props {
  count?: number;
  showThumbnails?: boolean;
  showDate?: boolean;
}

const { count = 5, showThumbnails = false, showDate = true } = Astro.props;

const { entries: posts } = await getEmDashCollection("posts", {
  limit: count,
  orderBy: { publishedAt: "desc" },
});
---

<ul class="recent-posts">
  {posts.map(post => (
    <li>
      {showThumbnails && post.data.featured_image && (
        <img src={post.data.featured_image} alt="" class="thumbnail" />
      )}
      <a href={`/posts/${post.slug}`}>{post.data.title}</a>
      {showDate && post.data.publishedAt && (
        <time datetime={post.data.publishedAt.toISOString()}>
          {post.data.publishedAt.toLocaleDateString()}
        </time>
      )}
    </li>
  ))}
</ul>
```

### Search Widget

```astro title="src/components/widgets/SearchForm.astro"
---
interface Props {
  placeholder?: string;
}

const { placeholder = "Search..." } = Astro.props;
---

<form action="/search" method="get" class="search-form">
  <input
    type="search"
    name="q"
    placeholder={placeholder}
    aria-label="Search"
  />
  <button type="submit">Search</button>
</form>
```

## Using Widget Areas in Layouts

The following example shows a blog layout with a sidebar widget area:

```astro title="src/layouts/BlogPost.astro"
---
import { getWidgetArea } from "emdash";
import WidgetRenderer from "../components/WidgetRenderer.astro";

const sidebar = await getWidgetArea("sidebar");
---

<div class="layout">
  <main class="content">
    <slot />
  </main>

  {sidebar && sidebar.widgets.length > 0 && (
    <aside class="sidebar">
      {sidebar.widgets.map(widget => (
        <WidgetRenderer widget={widget} />
      ))}
    </aside>
  )}
</div>

<style>
  .layout {
    display: grid;
    grid-template-columns: 1fr 300px;
    gap: 2rem;
  }

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

## Listing All Widget Areas

Use `getWidgetAreas()` to retrieve all widget areas with their widgets:

```ts
import { getWidgetAreas } from "emdash";

const areas = await getWidgetAreas();
// Returns all areas with widgets populated
```

## Creating Widget Areas

Create widget areas through the admin interface at `/_emdash/admin/widgets`, or use the admin API:

```http
POST /_emdash/api/widget-areas
Content-Type: application/json

{
  "name": "footer-1",
  "label": "Footer Column 1",
  "description": "First column in the footer"
}
```

Add a content widget:

```http
POST /_emdash/api/widget-areas/footer-1/widgets
Content-Type: application/json

{
  "type": "content",
  "title": "About Us",
  "content": [
    {
      "_type": "block",
      "style": "normal",
      "children": [{ "_type": "span", "text": "Welcome to our site." }]
    }
  ]
}
```

Add a component widget:

```http
POST /_emdash/api/widget-areas/sidebar/widgets
Content-Type: application/json

{
  "type": "component",
  "title": "Recent Posts",
  "componentId": "core:recent-posts",
  "componentProps": { "count": 5, "showDate": true }
}
```

## API Reference

### `getWidgetArea(name)`

Fetch a widget area by name with all widgets.

**Parameters:**

- `name` — The widget area's unique identifier (string)

**Returns:** `Promise<WidgetArea | null>`

### `getWidgetAreas()`

List all widget areas with their widgets.

**Returns:** `Promise<WidgetArea[]>`

### `getWidgetComponents()`

List available widget component definitions for the admin UI.

**Returns:** `WidgetComponentDef[]`
