Skip to content
Get started

Grids

feature-grid, bento-grid, card-carousel and pricing-table — laying several things out at once.

feature-grid

Equal-weight cards. The workhorse.

feature-grid

icon is a lucide name in kebab-case. Icons resolve on the server, so none of the icon library reaches the browser, and an unknown name fails the build naming the export it looked for.

bento-grid

Asymmetric tiles. Use it where a few things genuinely matter more than the others — that is the difference from feature-grid, which weights everything equally.

bento-grid

One engine, not five tools

Spans are cells on a four-column grid: normal is 1×1, wide 2×1, tall 1×2, large 2×2. On a tablet the grid drops to two columns and large becomes wide; on a phone everything is one column.

A tile with an image puts it at the foot and lets it fill the remaining height, which is what makes a large tile read as one composition rather than a card with a picture stuck under it.

A horizontally scrolling row, snapped, with the next card peeking past the container edge so the row reads as scrollable before anything moves.

card-carousel

The track is focusable so it can be scrolled by keyboard even when the cards inside are not links.

promo-grid

Large linked tiles, each carrying its own artwork and its own destination. This is the landing-page format a consumer product site uses: the tiles are the navigation, and the page has almost no prose of its own.

promo-grid

The engine

A folder of content is a website.

Point it at a directory and it builds the marketing pages and the docs.

Themes

Fourteen of them.

Widths are full or half, and two half tiles pair up on a wide viewport and stack below it. height picks short, tall or fullfull is a tile that fills the viewport, which is what makes a page of them read as a sequence of screens rather than a list.

A tile links in one of two ways, and they are exclusive: give it links for up to two labelled links laid over the artwork, or give it href to make the whole tile one big target. A link inside a link is not a thing.

pricing-table

One to four tiers.

pricing-table

Simple pricing

Per seat, per month.

  • Free

    $0forever

    For trying it out.

    Start free
    • Up to 3 users
    • Community support
  • Most popular

    Team

    $12per user / month

    For a team shipping regularly.

    Start a trial
    • Unlimited users
    • SSO
    • Priority support
  • Enterprise

    Contact us

    For everything else.

    Talk to us
    • Audit log
    • Custom retention
    • A person to call

Exactly one tier may be highlighted. A single "Contact us" tier renders gracefully — the layout does not assume three.

price is free text, so $12, From €9 and Contact us all belong. A bare number is accepted too and stringified, since YAML would otherwise hand it over as a number.

code-cards

A row of cards whose snippets are the point. For comparing options that differ mainly in what you have to write down — CI modes, deployment targets, auth strategies.

code-cards

Deployment

Three ways to put it somewhere

Each one is the same content folder. What differs is the file you commit to make it happen.

Static

A directory of files

Any host that serves files. Netlify, Cloudflare Pages, S3, a web server.

.github/workflows/docs.yml

- run: zel build docs --out dist
- uses: nwtgck/actions-netlify@v3
  with:
    publish-dir: dist

Container

An image that serves itself

For a platform that runs containers rather than hosting files.

.github/workflows/image.yml

- run: zel build docs --target image \
         --tag ghcr.io/acme/docs:$GITHUB_SHA
- run: docker push ghcr.io/acme/docs:$GITHUB_SHA

Wrapper

A Next.js app you own

When you want your own routes beside the generated ones.

package.json

{
  "scripts": {
    "build": "zellij prepare && next build"
  }
}

zel ci init writes whichever of these you pick, so the snippets are what you end up with rather than what you have to type.

Two to four cards. One is not a comparison, and a fifth pushes every card below the width at which its snippet still makes sense wrapped.

featured: true marks the one you recommend, with the accent rather than a badge — a badge needs a word, and the word is already in the card.

The snippets are <pre> elements, so they can be selected, copied and read aloud. They are not syntax-highlighted: Shiki runs at build time over MDX, and reaching it from a YAML section would mean either shipping a highlighter to the browser or threading a build step through the content loader — for snippets that are mostly keys and paths.

matrix

Two axes crossed, with a sentence in every cell. For the page whose real claim is that the grid is full — that every combination of the two choices is legal.

matrix

Every target, every build mode

The two choices are independent, which is the point a list of six bullets cannot make.

TargetRuns onLocalCIContainer
staticA directory of files.zel build docs --out distCommitted workflow runs the same command.Served by any web server image.
imageA container that serves itself.Needs a runtime — zel doctor says which.Built and pushed by the pipeline.The output is the container.

Both rows read the same content folder. Nothing about the content changes when the target does.

A list of the six combinations makes the same claim, and a reader has to count to check it. A grid makes it visible at a glance, which is the whole reason to spend the width.

It renders a real <table>: column names are <th scope="col">, each row's name is <th scope="row">. That is not markup pedantry — it is what makes a screen reader announce "passphrase, portal-key" before reading a cell, and the cell means nothing without it. A grid of divs looks identical and says, to anyone not looking at it, eight unrelated sentences.

Below 60rem the same table restacks into one block per row, each cell labelled by its column. Only the CSS changes; the markup and the header associations do not, so the reading order survives the layout.