Skip to Content

Pages stored as data, checked before they ship, and rendered in React or Astro.

You get a page that is checked for errors before it goes live and drawn by whichever runtime your site uses.

A block is one part of a page containing its type, props, and optional child blocks, and the CMS saves a list of these blocks.

Get started with ReactGet started with AstroGitHub

React
npm install @evanion/react-widget
Astro
npm install @evanion/astro-widget

The React renderer draws this page from the list of blocks below; move a block up or down and the page is composed again.

BaizeWeek 38
38Games in+6
31Sold+2
2.4 dTurnaround−0.3

Tonight at the tables

TableGamePartyState
OneBrass: BirminghamHanna Lind, +3Playing
TwoSpirit IslandOtto Ruane, +2Playing
ThreeRootSigrid Vall, +3Teaching
FourCrokinoleEmil Norrby, +1Booked, 20:00

Reprints on order

TitleDue
Wingspan, OceaniaTuesday
Hive PocketThursday
Azul, Summer PavilionNext week

itemsMove one and the page is composed again

[  { "id": "week", "type": "columns", "props": {}, "children": [    { "id": "intake", "type": "metric", "props": { "figure": "38", "label": "Games in", "delta": "+6" } },    { "id": "sold", "type": "metric", "props": { "figure": "31", "label": "Sold", "delta": "+2" } },    { "id": "turnaround", "type": "metric", "props": { "figure": "2.4 d", "label": "Turnaround", "delta": "−0.3" } }  ] },  { "id": "counter", "type": "columns", "props": {}, "children": [    { "id": "tables", "type": "tables", "props": { "title": "Tonight at the tables" }, "meta": { "span": 2 } },    { "id": "reprints", "type": "reprints", "props": { "title": "Reprints on order" } }  ] }]

Catch a broken page before it ships

You prevent layout failures by checking your data before it reaches the browser.

An editor renames a block type or leaves a required field empty and the CMS saves it. The renderer skips what it cannot draw and renders the rest, printing one warning in development and staying silent in production. This gap is easy to miss.

You call validateItems in a build script or webhook to find unknown types, blank required fields, or duplicate ids, with each problem naming the block. The function never throws. In Astro, you run this in a script before the build so any problem fails the build.

Change the saved page

No problems.

    If you skip the check, the React renderer draws this:

    Brass: Birmingham

    On the table tonight

    Wingspan

    Hive

    [
      { id: 'brass', type: 'listing', props: { title: 'Brass: Birmingham' } },
      { id: 'tonight', type: 'shelf', props: { heading: 'On the table tonight' },
        children: [
          { id: 'wingspan', type: 'listing', props: { title: 'Wingspan' } },
        ] },
      { id: 'hive', type: 'listing', props: { title: 'Hive' } },
    ]
    
    The problem list shows what validateItems finds, and the page below it shows what the React renderer draws if the check is skipped.
    Learn how to fail the Astro build on validation errors →

    Stop typos at compile time

    You catch coding errors while you write your components.

    A mistyped block type or a wrong prop in the code breaks the page.

    The React package checks every block and child block against the component props at compile time.

    npm install @evanion/react-widget
    const Listing = (props: { title: string; price: number }) => (
      <h3>
        {props.title}, {props.price} kr
      </h3>
    );
    
    const Shelf = (props: { heading: string; children?: React.ReactNode }) => (
      <section>
        <h2>{props.heading}</h2>
        {props.children}
      </section>
    );
    
    const { Widgets, defineItems } = createWidgets({
      components: { listing: Listing, shelf: Shelf },
    });
    
    // Props are checked for every item, nested children included.
    const page = defineItems([
      {
        id: 'tonight',
        type: 'shelf',
        props: { heading: 'On the table tonight' },
        children: [
          {
            id: 'brass',
            type: 'listing',
            props: { title: 'Brass: Birmingham', price: 649 },
          },
        ],
      },
    ]);
    
    // A mistyped type is an error in your editor, before anything runs.
    // @ts-expect-error 'listng' is not one of the widget types
    defineItems([{ id: 'hive', type: 'listng', props: { title: 'Hive' } }]);
    Read about defining blocks and nesting in React →

    Zero JavaScript for layout in Astro

    The server decides the arrangement of the page.

    The browser receives plain HTML for the page structure.

    The Astro package renders the block list to HTML on the server, and your own interactive components still ship their own code.

    See how to manage block order and nesting in Astro →

    Scope and boundaries

    Questions

    What is this library?
    It is a system to validate and render pages stored as a list of blocks. The core handles validation, the React package provides a React renderer, and the Astro package provides an Astro renderer.
    Does it include a CMS editor?
    No, you use your own CMS or database to save the blocks.
    Where does validation run in Astro?
    You run the check in a script before the build process; the script exits non-zero on any problem and stops the build.
    Do the same blocks render in React and Astro?
    Yes, the same block array renders through either package.
    What happens when a block is bad at render time?
    The renderer skips the bad block and renders the rest of the page. It prints one warning in development and stays silent in production, which is why the check runs before shipping.

    Start building

    Choose your framework to begin. Use the guides to set up your first renderer.

    Get started with ReactGet started with AstroGitHub