Widget Areas
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
Section titled “Querying Widget Areas”Use getWidgetArea() to fetch a widget area by name:
---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
Section titled “Widget Area Structure”A widget area contains metadata and an array of widgets:
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
Section titled “Widget Types”EmDash supports three widget types:
Content Widgets
Section titled “Content Widgets”Rich text content stored as Portable Text. Render using the PortableText component:
---import { PortableText } from "emdash/ui";---
{widget.type === "content" && widget.content && ( <div class="widget-content"> <PortableText value={widget.content} /> </div>)}Menu Widgets
Section titled “Menu Widgets”Display a navigation menu within a widget area:
---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
Section titled “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
Section titled “Rendering Widgets”Create a reusable widget renderer component:
---import { PortableText } from "emdash/ui";import { getMenu } from "emdash";import type { Widget } from "emdash";
// Import your widget componentsimport 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
Section titled “Example Widget Components”Recent Posts Widget
Section titled “Recent Posts Widget”---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.featuredImage && ( <img src={post.data.featuredImage} 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
Section titled “Search Widget”---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
Section titled “Using Widget Areas in Layouts”The following example shows a blog layout with a sidebar widget area:
---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
Section titled “Listing All Widget Areas”Use getWidgetAreas() to retrieve all widget areas with their widgets:
import { getWidgetAreas } from "emdash";
const areas = await getWidgetAreas();// Returns all areas with widgets populatedCreating Widget Areas
Section titled “Creating Widget Areas”Create widget areas through the admin interface at /_emdash/admin/widgets, or use the admin API:
POST /_emdash/api/widget-areasContent-Type: application/json
{ "name": "footer-1", "label": "Footer Column 1", "description": "First column in the footer"}Add a content widget:
POST /_emdash/api/widget-areas/footer-1/widgetsContent-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:
POST /_emdash/api/widget-areas/sidebar/widgetsContent-Type: application/json
{ "type": "component", "title": "Recent Posts", "componentId": "core:recent-posts", "componentProps": { "count": 5, "showDate": true }}API Reference
Section titled “API Reference”getWidgetArea(name)
Section titled “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()
Section titled “getWidgetAreas()”List all widget areas with their widgets.
Returns: Promise<WidgetArea[]>
getWidgetComponents()
Section titled “getWidgetComponents()”List available widget component definitions for the admin UI.
Returns: WidgetComponentDef[]