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.
Tonight at the tables
| Table | Game | Party | State |
|---|---|---|---|
| One | Brass: Birmingham | Hanna Lind, +3 | Playing |
| Two | Spirit Island | Otto Ruane, +2 | Playing |
| Three | Root | Sigrid Vall, +3 | Teaching |
| Four | Crokinole | Emil Norrby, +1 | Booked, 20:00 |
Reprints on order
| Title | Due |
|---|---|
| Wingspan, Oceania | Tuesday |
| Hive Pocket | Thursday |
| Azul, Summer Pavilion | Next 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" } } ] }]Your own CMS
You bring your own CMS or database to provide the list of blocks.
Multi-runtime rendering
One block list renders in both React and Astro.
No dependencies
The core library has no dependencies to install or update.
The core →Server component ready
The React package works in React Server Components.
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.
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' } },
]
validateItems finds, and the page below it shows what the React renderer draws if the check is skipped.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-widgetconst 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
No editor
The library provides no editor; you bring your own CMS or database.
Read the core overview →Astro prop checking
Astro has no compile-time prop checking, so you use
Setup the Astro build gate →validateItemswithrequiredto guard your data.Meta validation
The library does not validate
See what the validator reports →metaproperties at runtime.
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.