Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mullion, a fictional analytics product, rendered entirely from this theme's tokens, components and grid layer

A bento grid: compartmentalized cells at mixed spans, one gap holding them together, each cell a single self-contained thing. A stat, a feature, a plot, a quote. Apple's product pages sit here. Linear's feature sections sit here. Most of what gets tagged bento UI on Dribbble sits here.

Bento is a layout archetype, not a surface one

This is the thing worth knowing before you install anything.

Neobrutalism is a look: borders, offsets, flat fills. Glassmorphism is a look: blur, translucency, light edges. Bento is not a look. It is a way of dividing a page, and it composes with a surface register rather than being one. A bento grid can be rendered hard-bordered, or glassy, or Swiss-quiet, and it stays a bento grid through all three.

Which means a repository that ships bento owes you two separable things: the grid choreography, and a surface register you can replace. That is how this one is built, and demo/reskin.html is the proof. It renders one arrangement three times under three surface registers, with every rule in components/grid.css untouched between them.

The position map below describes the default surface. It does not describe the layout, which has no single position.

The demo

The screenshot above is demo/index.html, a fictional analytics product called Mullion. Open it live, or clone the repo and open the file. There is no build step, no framework, no node_modules, and no server to start.

The demo declares no color of its own. It links tokens/tokens.css, components/components.css and components/grid.css and reads every value from them, so it stays honest about what the theme actually produces.

Two more pages are worth opening. components/index.html renders all six components and all three arrangements with the markup to copy. demo/reskin.html is the composition proof described above, and it is the one to open first: see the re-skin live.

Position map

A visual style is a set of coordinates, not a mood. This theme's default surface sits at one point in the creative direction framework, which sets brand direction on four axes. Here is where that surface lands and what each choice pays for.

Axis Position What the position buys
Tone register Professional A page that can be dense without being loud. Twelve compartments competing for attention need the copy inside them to stop asking for it.
Aesthetic philosophy Polished Standard The convention a reader already knows how to scan. The composition is the differentiator, so the surface under it declines to be.
Audience relationship Peer A 20px corner. Square would address the reader as an operator reading a table; a pill corner would address them as a child.
Sensory ambition Considered One accent cell per arrangement and a two pixel lift on hover. The craft is in the arithmetic of the mosaic rather than on top of it.

Those four position names are the exact strings the framework uses. If you want the long version of any of them, the links go to the position page.

Read the map as a description of what ships in tokens/tokens.css, not as a claim about bento. Swap the surface register and three of those four coordinates move; the grid does not.

Quick start

Clone once, then pick the path that matches your stack.

git clone --depth 1 https://github.com/rampstackco/bento-grid-theme

Plain CSS. Copy the two directories and link the three files in order. This is the whole install.

cp -r bento-grid-theme/tokens bento-grid-theme/components your-project/styles/
<link rel="stylesheet" href="/styles/tokens/tokens.css" />
<link rel="stylesheet" href="/styles/components/components.css" />
<link rel="stylesheet" href="/styles/components/grid.css" />

The second and third lines are independent of each other. Take grid.css without components.css if you have your own cards and want the mosaic under them, which is the combination this repo expects to be most common.

Tailwind v4. One import for the tokens. theme.css pulls in tokens.css and maps it onto Tailwind's theme namespaces, so you get bg-bn-surface, rounded-bn-cell, gap-bn-gap, text-bn-stat.

@import "tailwindcss";
@import "./styles/tokens/theme.css";
@import "./styles/components/grid.css";

The adapter maps the column counts too, so grid-cols-bn, grid-cols-bn-md and grid-cols-bn-sm exist. What stays a stylesheet on either version is the span utilities and the reflow, because those are rules about placement rather than values.

One utility in the v4 adapter carries a caveat rather than a promise. border-bn comes from --border-width-*, which generates on the version this was verified against but is absent from Tailwind's documented namespace table, so it has no compatibility guarantee. The route to swap to is border-(length:--bn-border-width), which compiles to the same declarations.

Keep the length: hint if you do swap. border-(--bn-border-width) and border-[var(--bn-border-width)] both compile, both produce a valid rule, and both set border-color instead of border-width. Tailwind reads a bare variable in the border-* family as a color, so you get a border color of 1px, which is dropped at computed-value time, and the element silently keeps the border it already had. No error, no missing class. The compiled output for all of these is in the comments at the top of theme.css.

On v3 the question does not arise: borderWidth is a first-class theme key and preset.js uses it.

Tailwind v3. Load the tokens in your stylesheet, then register the preset.

@import "./styles/tokens/tokens.css";
@import "./styles/components/grid.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
// tailwind.config.js
module.exports = {
  presets: [require("./styles/tokens/preset.js")],
  content: ["./src/**/*.{html,js,jsx,ts,tsx}"],
};

Already on shadcn/ui. The tokens are namespaced --bn-* so they will not clobber yours. Bridge the two in your global stylesheet and shadcn's components inherit the register, then put grid.css around them:

:root {
  --background: var(--bn-ground);
  --foreground: var(--bn-ink);
  --card: var(--bn-surface);
  --primary: var(--bn-primary);
  --primary-foreground: var(--bn-primary-ink);
  --border: var(--bn-border-color);
  --ring: var(--bn-ring);
  --radius: var(--bn-radius-cell);
}

Where the reasoning lives

tokens/tokens.css is the single source of truth. Every literal value in the theme appears there exactly once; theme.css and preset.js hold no values of their own and point back at it with var(). Change a hex there and the demo, the components and both Tailwind adapters follow.

The file is annotated. Each group carries a comment naming the axis the choice serves and why, so the radius tokens explain themselves:

/* RADIUS
   Audience relationship axis, and the single largest lever on whether a
   grid reads as bento. Below about 12px a mosaic reads as a table; above
   about 28px it reads as a toy. */

CUSTOMIZE.md is the half-finished layer, and it is half-finished deliberately. It documents retheming as axis moves rather than as a color picker: pick an axis, move along it, change the two or three tokens that carry the move. One move is worked through with before and after values. Two more are sketched so the format is obvious enough to finish yourself.

The grid layer

components/grid.css holds what a token file cannot. A gap and a radius are values; a mosaic that collapses without leaving holes in it is a set of rules about how those values get placed, and rules do not fit in custom properties.

It ships a container, five span utilities read as width by height (bn-cell-2x1 is two columns wide and one row tall), the reflow from four columns to two to one, and three named arrangements. The arrangements are the copy-paste part: put bn-arr-stats-board on a container, give it seven cells, and none of the cells need a class.

Three things in that file are worth reading before you write your own mosaic.

The reflow is where bento layouts break, and it breaks at two columns rather than one. Auto-placement leaves a hole when a wide cell does not fit in what is left of the current row. At four columns a run of three 1x1 cells is whole; at two columns it orphans. Each arrangement carries a ledger comment showing every row at both breakpoints, and every row sums.

Below 640px no span utility is declared at all. In a one-column grid a leftover grid-column: span 2 does not collapse to one column, it makes the grid generate an implicit second column. Measured at 390px with a 14px gap, the tracks come out 376px 0px rather than 390px: the phantom track is zero wide and still takes a gap, so every cell that is not spanning loses 14px and stops meeting the container's right edge. Nothing overflows, which is what makes it slow to find. Declaring the spans only inside min-width queries means there is nothing to unset.

Row heights are minmax(floor, auto) rather than a flat height. A flat grid-auto-rows gives every row one track height and a cell whose content needs more spills past its own rounded corner. That is the most common bento bug in the wild, and it costs one keyword to not have.

Composing with a surface register

The main demo runs this repo's own neutral tokens. demo/reskin.html runs the same arrangement twice more, once hard-bordered and once Swiss-quiet, and names what moved: twenty two surface tokens in one case, twenty three in the other, and nothing in the grid layer either time.

Both alternate registers are declared in tokens.css under a data-surface attribute, and both have now been diffed against the real sibling token files, neobrutalism-theme and swiss-style-theme. Every value names the sibling token it came from.

The diff was worth running. The hard block needed no correction; all twenty two of its values already matched neobrutalism. The quiet block was wrong in six places, and one of them mattered: the cell border was ink here and is a mid grey rule in the real Swiss theme, so this repo had been rendering a Swiss register with a boundary the register does not use. That is corrected, and the re-skin section below is a render of the corrected values.

The values stay declared here rather than imported, because an import would put either a network fetch or an assumption about your directory layout behind a demo that opens from a file URL with nothing installed. tokens.css states that trade in full, along with the mapping to use if you do have the sibling checkouts and want the live binding.

If you are pairing bento with a translucent surface treatment rather than either of these, that lives in glassmorphism-theme. It is the one sibling whose register this page cannot demonstrate, because glass shows what is behind it and a re-skin frame has nothing behind it but the page.

Consuming this from a Claude skill. The design-standards skill asks for a project's design tokens as a required input and offers to define a working set when none exist. Point it at tokens/tokens.css instead. The file already covers every category the skill asks for, in the order it asks: color with measured contrast ratios, spacing scale, type scale, radius. Every text pairing in it clears WCAG AA in all three registers, and the ratios are in the comments so the skill's contrast pass has nothing left to compute.

Adjacency: this is not a card grid

A bento grid and a responsive card grid produce the same HTML and different pages, and the difference is worth naming because reaching for the wrong one is the most common way a bento ends up looking like a dashboard nobody designed.

A card grid is uniform. Every card is one column wide, the cards are interchangeable, and the grid is a container for a list whose length the page does not control. Search results are a card grid. A blog index is a card grid. Adding a fourteenth item changes nothing about the composition, which is the property that makes it the right tool.

A bento is composed. The spans are unequal on purpose, the cell in the 2x2 is the one the page is about, and the count is fixed because the arrangement was designed around it. Adding a fourteenth item breaks it. That fragility is the cost of the composition, and paying it for content that is genuinely a list gets you a mosaic that looks arbitrary, because it is.

The test is whether you could shuffle the cells without losing anything. If you could, you want a card grid.

License and questions

MIT. See LICENSE. Use it commercially, fork it, rename the tokens, ship it. No attribution required.

Issues and pull requests are welcome here. For questions, ideas, and anything conversational, use the discussions on the claude-skills repo, which is where all discussion for these repos lives.

About

Bento grid layouts you can grab and go: responsive span primitives, collapse rules, named arrangements, and re-skins showing the grid under different styles. Tailwind and shadcn.

Topics

Resources

Security policy

Stars

1 star

Watchers

1 watching

Forks

Contributors

Languages