Page data, wrappers and nested items
@evanion/astro-widget 0.3.0 and its patch releases up to 0.3.4, the version on npm. Values on this page were produced by running the source at 2ee3e7e, a later commit than the 0.3.0 tag, and are not re-executed.
Widgets.astro allows you to inject page-level data into every widget via ctx, and standardize the visual shell of your widgets using chrome. It also supports complex, nested layouts where one widget acts as a container for others.
Providing Page-Level Data with ctx
Astro components run once at build time and do not have a shared React-style context. To provide global data—such as the store’s name, the current currency, or a user’s locale—to every widget in a region, use the ctx prop on <Widgets />.
Widgets.astro passes this object to every widget as a prop named ctx.
---
interface Props {
copies: number;
ctx: { store: string };
}
const { copies, ctx } = Astro.props;
---
<p>{copies} copies in {ctx.store}</p>In your page, you simply pass the data once: <Widgets ctx={{ store: 'Gothenburg' }} items={items} registry={registry} />. Every widget in that region now has access to the store name without it being duplicated in the CMS data for every single item.
Wrapping Widgets with chrome
The chrome prop allows you to define a shared wrapper component that surrounds every widget. This is where you handle layout concerns—like adding a <section> tag, applying a background color, or setting a grid span—keeping the widget components themselves focused on their specific data.
The chrome.item component receives the item’s type, id, and meta, but none of its props.
---
interface Props {
type: string;
id: string;
meta?: { background?: string };
}
const { type, id, meta } = Astro.props;
---
<section
id={id}
data-widget-type={type}
class:list={['widget', meta?.background && `bg-${meta.background}`]}
>
<slot />
</section>Essential Rule: The <slot />
A chrome component must render a <slot />. This is where Astro injects the actual widget. If you omit the <slot />, the widget inside the wrapper will simply disappear from the page.
Rendering Nested Items
In some cases, a widget is not a leaf node but a container for other widgets (e.g., a “Featured Shelf” containing “Game Cards”).
Unlike @evanion/react-widget, which renders children automatically, Widgets.astro passes the children items to the parent component as raw data. To render them, the parent widget must render its own <Widgets /> component using its own registry.
Example: A Game Grid
A GameGrid widget might render a title and then a grid of children:
---
import Widgets from '@evanion/astro-widget/components/Widgets.astro';
import type { AnyWidgetItem } from '@evanion/astro-widget';
import { cardRegistry } from '../card-registry';
interface Props {
title: string;
children?: AnyWidgetItem[];
}
const { title, children = [] } = Astro.props;
---
<section>
<h2>{title}</h2>
<Widgets items={children} registry={cardRegistry} />
</section>To make this work, you define a separate registry for the children (e.g., cardRegistry) and pass it to the nested <Widgets /> call.
The Registry Hierarchy
Because the nested <Widgets /> uses its own registry, it only renders children that match that specific registry. If a child item’s type is not in the cardRegistry, it will be skipped, even if that type is supported by the page’s main root registry.
Syncing the Build Gate
If you add new widget types or nested containers, you must update your validation script. validateItems scans the entire tree, including all nested children.
In your scripts/check-content.ts file, add your new types (e.g., 'game-grid', 'game-card') to the known list. You can also add them to the required map to ensure that a container like game-grid always has a title before the build is allowed to complete.