Skip to Content
React WidgetChrome, Meta and Ctx

Chrome, meta and ctx

@evanion/react-widget 0.3.0 and its patch releases up to 0.3.3, the version on npm. Values on this page were produced by running the source at f9b1d93, a later commit than the 0.3.0 tag, and are not re-executed.

createWidgets takes a chrome option that allows you to customize the markup surrounding each widget and the entire region. This separates the visual “shell” of your page from the actual widget components.

The Problem: Mixing Layout with Business Logic

A common mistake is to bake layout logic (like grid spans or aria-labels) directly into a widget component. This makes the component less reusable—a ListingCard should care about the game’s title and price, not whether it occupies two columns in a specific grid.

@evanion/react-widget solves this by introducing “Chrome.”

Customizing the Region and Item Shells

The “Chrome” consists of two components:

  • chrome.wrapper: The outermost element of the region (e.g., a <section>). It receives the children and the full list of items.
  • chrome.item: The element that wraps every individual widget. It receives the children (the widget itself), the item’s id, type, and its meta object.

The package exports DefaultWrapper and DefaultItem as starting points. A custom chrome typically spreads these defaults to maintain the essential data-widget-* attributes.

const { , } = ({ : { : }, : { : () => < {...} ="New in" />, : () => < {...} ="slot" />, }, }); const = ([ { : 'azul', : 'listing', : { : 'Azul' } }, ]); const = (< ={} />); // -> '<section aria-label="New in"><div data-widget-id="azul" data-widget-type="listing" class="slot"><h3>Azul</h3></div></section>'

Developer Tip: Keep the data-widget-id and data-widget-type attributes on the element. These are essential for CMS click-to-edit overlays and automated E2E tests.

Positioning Widgets with meta

While a widget’s props contain the data, the meta property is used for placement. Because meta is passed to the chrome.item and never to the widget’s props, a widget renders identically regardless of its position.

You can use the WidgetItemComponent type to annotate your chrome and ensure that defineItems validates the meta fields for every item.

import type { } from '@evanion/react-widget'; type = { : number; ?: number }; const : <> = ({ , , ... }) => ( < {...} ={{ : `${?. ?? 'auto'} / span ${?. ?? 1}`, }} > {} </> ); const { , } = ({ : { : }, : { : }, }); const = ([ { : 'root', : 'listing', : { : 'Root' }, : { : 2, : 2 }, }, // @ts-expect-error GridCell reads `column`, and `col` is not a key of GridMeta { : 'hive', : 'listing', : { : 'Hive' }, : { : 1 } }, ]); const = (< ={} />); // -> '<section><div data-widget-id="root" data-widget-type="listing" style="grid-column:2 / span 2"><h3>Root</h3></div><div data-widget-id="hive" data-widget-type="listing" style="grid-column:auto / span 1"><h3>Hive</h3></div></section>'

In this example, GridCell reads a column property from meta. If an item’s meta is missing a key, the chrome should provide a fallback (e.g., grid-column: auto).

Sharing Page-Level Data via ctx

Sometimes every widget in a region needs the same data—such as the current currency or a user’s locale. Rather than passing this data into every single item’s props in the CMS, you can use the ctx prop on the <Widgets> component.

const = (: { : string; : { : string } }) => ( <> {.}, {..} </> ); const { , } = ({ : { : }, }); const = ([ { : 'root', : 'listing', : { : 'Root' } }, ]); const = < ={{ : 'SEK' }} ={} />; const = (); // -> '<section><div data-widget-id="root" data-widget-type="listing"><h3>Root, SEK</h3></div></section>'

Widgets injects this ctx object into every widget’s props. Because ctx is added after the item’s own props, a CMS payload cannot accidentally override this global context.

Optimizing Long Regions

Widgets renders all items in the array. For server-rendered pages with many widgets, you can improve performance using the CSS property content-visibility: auto on your item chrome. This tells the browser to skip the layout and paint of off-screen widgets.

const { , } = ({ : { : }, : { : () => ( < {...} ={{ : 'auto', : 'auto 240px', }} /> ), }, }); const = ([ { : 'crokinole', : 'listing', : { : 'Crokinole' } }, ]); const = (< ={} />); // -> '<section><div data-widget-id="crokinole" data-widget-type="listing" style="content-visibility:auto;contain-intrinsic-size:auto 240px"><h3>Crokinole</h3></div></section>'

For client-side lists that are long enough to require virtualization, you can implement it within a custom 'use client' chrome.wrapper. Since the wrapper receives the items array in order, it can easily measure and window the widgets.

Isolating Slow or Failing Widgets

Because every widget is wrapped in your custom chrome, you have a perfect hook to isolate them. Suspense and error boundaries shows how to give each widget a loading fallback through chrome.suspenseFallback and put an error boundary inside the item chrome, ensuring that a single failing widget doesn’t crash the entire page.

Last updated on