@bnomei/emdash-bento
v0.2.1
Published
Bento grid field widget for EmDash JSON fields, using Block Builder for nested blocks.
Maintainers
Readme
@bnomei/emdash-bento
Bento grid field widget for EmDash JSON fields.
@bnomei/emdash-bento is a native EmDash plugin for row and
column layout data stored as plain JSON. It registers the bento:layouts
admin widget, reuses @bnomei/emdash-blocks for nested column content,
and exports helpers for Astro renderers to prepare visible rows and translate
fractional spans into a 12-column grid. Use it when editors need controlled
layout composition while templates still own the frontend markup.
What It Provides
- Native EmDash plugin factory:
bentoPlugin(). - JSON field widget:
bento:layouts. - One editable layout pattern string per row.
- Nested block controls through
options.blockDefinitions. - Admin UI built with Kumo UI with full light and dark mode support.
- Frontend helpers:
visibleLayoutRows()andspanToGridColumns().
Install
npm install @bnomei/emdash-bento @bnomei/emdash-blocksRegister both plugins in astro.config.mjs:
import emdash from "emdash/astro";
import { bentoPlugin } from "@bnomei/emdash-bento";
import { blockBuilderPlugin } from "@bnomei/emdash-blocks";
export default {
integrations: [
emdash({
plugins: [blockBuilderPlugin(), bentoPlugin()],
}),
],
};Field Widget
Use the widget on an EmDash json field:
{
"slug": "layouts",
"label": "Grid",
"type": "json",
"widget": "bento:layouts",
"options": {
"blockDefinitions": [
{
"type": "heading",
"label": "Heading",
"props": [{ "key": "text", "label": "Text", "type": "text" }]
},
{
"type": "text",
"label": "Text",
"props": [{ "key": "text", "label": "Text", "type": "markdown" }]
},
{
"type": "image",
"label": "Image",
"props": [{ "key": "image", "label": "Image", "type": "media" }]
}
]
}
}Stored Value
type LayoutBuilderValue = Array<{
id: string;
layout: string;
settings?: Record<string, unknown>;
columns: Array<{
id: string;
span: string;
blocks: BlockBuilderValue;
}>;
}>;Empty layout fields stay empty when the admin widget mounts, so opening an untouched entry does not write a default row or mark the field dirty. The widget shows an empty state with the Add Layout button, and clicking that button creates the first editable row.
Each row stores exactly one layout pattern. The pattern is a comma-separated
list of column spans, for example 1/1, 1/2, 1/2, or 1/1, 1/3, 2/3.
Editing the pattern updates the row's columns by position, preserving existing
column blocks where a matching column index still exists. If the edited pattern
has fewer spans than the row already has columns, columns beyond that pattern
are removed. The per-column span values mirror the row pattern and are stored
so frontend renderers can read the same data directly.
Editors can also append a column with the row-level Add Column button. New
columns start as 1/1, and the row pattern updates to match the visible
columns.
The Add Layout button creates a new row with 1/1, 1/2, 1/3; editors can
change that row's layout pattern after it has been added.
Each column header also exposes a sorted width select and a remove button. The select choices are the unique spans in that row's current layout pattern. Layouts can be reordered with up/down controls. Columns can be reordered with left/right controls.
The admin widget renders columns on a 12-column CSS grid where 1/2 spans 6 grid columns and 1/3 spans 4 columns. If a row's widths add up to more than 1/1, the extra columns wrap to the next line. The widget accepts comma-separated fractions with denominators up to twelfths, so editors can type one row pattern such as 1/1, 1/2, 1/2, or 1/1, 2/3, 1/3 and override it whenever the layout needs to change.
Nested blocks use @bnomei/emdash-blocks directly. Pass
options.blockDefinitions to control the available nested block types and their
typed prop controls. Standalone block fields and layout column blocks share the
same definition shape.
Frontend Rendering
Render layout rows as a 12-column CSS grid. The package exports helpers for the common data preparation and span calculation:
import { spanToGridColumns, visibleLayoutRows } from "@bnomei/emdash-bento";
import { visibleBlocks } from "@bnomei/emdash-blocks";
const rows = visibleLayoutRows(entry.layouts);{
rows.map((row) => (
<section class="layout" data-layout-id={row.id}>
{row.columns.map((column) => (
<div
class="column"
style={`--span: ${spanToGridColumns(column.span)}`}
data-column-id={column.id}
>
{visibleBlocks(column.blocks).map((block) => renderBlock(block))}
</div>
))}
</section>
))
}.layout {
display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr));
}
.column {
grid-column: span var(--span) / span var(--span);
}The helpers accept native Bento values. Migration from other systems should happen before values are saved into EmDash.
Package Surface
- ESM entry:
@bnomei/emdash-bento. - Admin entry:
@bnomei/emdash-bento/admin. - Type declarations are included from
dist/. - Peer dependencies:
@bnomei/emdash-blocks^0.2.0,emdash>=0.17.0,react^18.0.0 || ^19.0.0,react-dom^18.0.0 || ^19.0.0,@cloudflare/kumo^2.5.0, and@phosphor-icons/react^2.1.10.
Status
This package ships as a native EmDash plugin because the editor is a trusted React admin field widget. Package exports point at Viteplus vp pack-built dist/ JavaScript and declarations; use npm run build for the production pack and npm run pack:check for the publint-verified pack check.
Related Packages
@bnomei/emdash-blocksprovides the nested block editor used by Bento columns.@bnomei/emdash-fieldsprovides smaller JSON-backed field widgets.
License
MIT.
Screenshot

