Widget Areas

On this page

A widget area is a named position in a site template. Editors choose what appears there, while the template controls where the area sits and how its output is styled.

Use a widget area for content that should stay synchronized wherever the area appears, such as a sidebar, a footer column, or a promotional message. Use a section when an editor should insert and then customize an independent copy inside an entry.

Add a widget area

Create and fill widget areas in Widgets in the EmDash admin.

  1. Click Add Widget Area. Enter the name the template will query, a label for the admin, and an optional description of where it appears.

  2. Drag a widget from Available Widgets into the new area.

  3. Configure the widget, then drag the widgets within the area to set their order.

The available widget types are:

  • Content renders Portable Text entered by an editor.
  • Menu renders a menu selected by name.
  • Component renders one of the built-in components: recent posts, categories, tags, search, or archives.

The component settings in the admin control details such as item limits, dates, counts, and search placeholder text.

The following built-in component widgets are available:

ComponentWhat it renders
core:recent-postsRecent posts, with optional dates and thumbnails
core:categoriesCategory links and optional entry counts
core:tagsA limited list of tag links and optional counts
core:searchA search form that submits to /search
core:archivesMonthly or yearly post archive links

Place the area in a template

Import WidgetArea from emdash/ui. The component fetches the named area, preserves the configured order, and renders nothing when the area is missing or empty.

The following layout places a sidebar area beside the page content:

---
import { WidgetArea } from "emdash/ui";
---

<div class="page-with-sidebar">
  <main>
    <slot />
  </main>

  <aside aria-label="Sidebar">
    <WidgetArea name="sidebar" class="sidebar-widgets" />
  </aside>
</div>

WidgetArea adds widget-area and the supplied class to its wrapper. Each item uses the widget, widget__title, and widget__content classes. Content, menu, and built-in component widgets add more specific classes beneath them.

Astro component styles are scoped by default. Use a global style block when styling the markup rendered inside WidgetArea:

<style is:global>
  .page-with-sidebar {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 18rem;
    gap: 2rem;
  }

  .sidebar-widgets {
    display: grid;
    gap: 1.5rem;
  }

  .sidebar-widgets .widget__title {
    margin-block-end: 0.75rem;
  }

  @media (max-width: 48rem) {
    .page-with-sidebar {
      grid-template-columns: 1fr;
    }
  }
</style>

Menu, category, and tag widgets query data in the current request locale. Menu references also resolve to the translated content or term when one exists.

The built-in widget renderer uses the root-relative URL returned by the menu or taxonomy helper. It does not add Astro’s locale prefix. On a multilingual site, render navigation and taxonomy lists with the menu rendering pattern or taxonomy list pattern when those links need locale prefixes. Content, search, recent-post, and archive widgets can still use the standard WidgetArea component.

Render an area yourself

Call getWidgetArea() when the site needs markup that the built-in renderer does not provide. The following example renders an area containing content and menu widgets, and adds Astro’s locale prefix to menu links.

First, fetch the area and pass each configured widget to a renderer:

---
import { getWidgetArea } from "emdash";
import LocalizedWidget from "./LocalizedWidget.astro";

interface Props {
  name: string;
}

const area = await getWidgetArea(Astro.props.name);
---

{area && area.widgets.length > 0 && (
  <div class="widget-area" data-widget-area={area.name}>
    {area.widgets.map((widget) => (
      <LocalizedWidget widget={widget} />
    ))}
  </div>
)}

Then handle content and menu widgets explicitly:

---
import { getMenu } from "emdash";
import type { Widget } from "emdash";
import { PortableText } from "emdash/ui";
import { getRelativeLocaleUrl } from "astro:i18n";

interface Props {
  widget: Widget;
}

const { widget } = Astro.props;
const locale = Astro.currentLocale;
const menu = widget.type === "menu" && widget.menuName
  ? await getMenu(widget.menuName, { locale })
  : null;

function menuHref(url: string) {
  return locale && url.startsWith("/")
    ? getRelativeLocaleUrl(locale, url)
    : url;
}
---

{widget.type !== "component" && (
  <section class="widget" data-widget-id={widget.id}>
    {widget.title && <h3>{widget.title}</h3>}

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

    {widget.type === "menu" && menu && (
      <nav aria-label={widget.title}>
        <ul>
          {menu.items.map((item) => (
            <li>
              <a
                href={menuHref(item.url)}
                target={item.target}
                rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
              >
                {item.label}
              </a>
            </li>
          ))}
        </ul>
      </nav>
    )}
  </section>
)}

This focused renderer produces no output for component widgets. Keep component widgets in the standard WidgetArea, or add explicit component-ID cases for the site components you support.

The runtime API reference documents getWidgetArea() and getWidgetAreas(). For programmatic changes, authenticate with a Bearer token and add X-EmDash-Request: 1 to every state-changing request. See the widget area endpoints for request bodies and responses.