VI
VI layout
Renders one VI canvas (the wordmark, its line, grid-snapped text, and optional imagery) from serializable config and content, so a report cover or social tile drops straight in from a generated brief.
Install
npx shadcn@latest add @alix/viLayoutPreview
How CEOs are rethinking growth in a slower market
Leaders are reallocating capital toward resilience.
Usage
import { VILayout } from "@/components/ui/viLayout";
<VILayout
config={{ format: "square", size: { line: 6 }, anchorH: "left", anchorV: "header", line: "auto" }}
content={{
title: "How CEOs are rethinking growth in a slower market",
subtitle: "Leaders are reallocating capital toward resilience.",
meta: ["alixpartners.com"],
}}
textLayout={{ block: { colStart: 7, colEnd: 19, anchorY: 0.44, titleSize: 6 }, meta: { align: "left" } }}
/>Examples
The skeleton
Config alone draws the shell: the white canvas, the black wordmark sized so its X lands on the grid line, and the hairline it sits on (here with the answering echo). No content, no imagery.
<VILayout
config={{
format: "a4",
size: { line: 3 },
anchorH: "left",
anchorV: "header",
line: "auto",
echo: true,
}}
/>A filled template
Add content and a text layout snapped to the same grid and the skeleton becomes a report cover: the title hung one clear column off the line and capped at a grid line as its measure, the engagement on the footer row.
Restoring margin in a slower market
A ninety-day plan to find the cash and hold the line.
<VILayout
config={{ format: "a4", size: { line: 3 }, anchorH: "left", anchorV: "header", line: "auto", echo: true }}
content={{
title: "Restoring margin in a slower market",
subtitle: "A ninety-day plan to find the cash and hold the line.",
meta: ["Northwind Industries", "June 2026"],
}}
textLayout={{ block: { colStart: 4, colEnd: 8, anchorY: 0.5, titleSize: 6 }, meta: { align: "left" } }}
/>Imagery flips the ink
A full-bleed image with tone: "dark" flips every ink (line, wordmark, and type) to white. The src is a plain string, never a node, so a CMS or a brief supplies it and the consumer's own optimiser feeds it.

The city of tomorrow
Where next for commercial real-estate?
<VILayout
config={{ format: "a4", size: { line: 3 }, anchorH: "left", anchorV: "center", line: "auto" }}
content={{ title: "The city of tomorrow", subtitle: "Where next for commercial real-estate?" }}
textLayout={{ block: { colStart: 4, colEnd: 10, anchorY: 0.035, titleSize: 8 } }}
media={{ src: "/vi/arch-simone-s3z.webp", tone: "dark" }}
/>Driven from the template library
The point of the flat contract: config, content, textLayout and media are all serializable data, the exact shape a generated brief fills. A pack generator maps a whole format family straight onto the renderer with no per-artifact wiring.
Resilience is a capability, not a cost centre.
£14m
Recoverable EBITDA from decisions already made and then left unenforced.

Margin leaks one discount at a time
The counter gives on price because no one set the floor.
How CEOs are rethinking growth in a slower market
Leaders are reallocating capital toward resilience.
Disruption Index
Annual B2B growth webinar
import { VILayout } from "@/components/ui/viLayout";
import { TEMPLATES_BY_FORMAT } from "@/lib/vi/content";
// A generated brief produces the flat { config, text, sample, media } shape;
// spread it straight onto the renderer.
{TEMPLATES_BY_FORMAT("square").map((t) => (
<VILayout
key={t.id}
config={t.config}
content={t.sample}
textLayout={t.text}
media={t.media}
/>
))}When to use it
Use it for
- Any branded canvas artifact off the website (report and slide covers, section dividers, social tiles, banners) where the wordmark and type are placed by grid rule, not by eye.
- Rendering from flat, serializable config + content: the shape a generated brief or pack produces, spread straight in.
- Over imagery: set media.tone to "dark" and every ink flips white in one prop, with the src staying a plain string a CMS or optimiser feeds.
Reach for something else
- In-app UI or any theme-aware surface. This is an artifact renderer, white-canvas by design and deliberately not theme-aware; use the web component layers (Surface, Panel) instead.
- Positioning the geometry yourself. The placement is never the caller's; express it in grid terms (`{ line: k }` / `{ span: n }`, named anchors) and the logo's size and line derive.
- Sizing type below the legible minimum. The per-format type floors clamp automatically, so don't fight them with a smaller titleSize.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| config | VIConfig | – | The geometry input, all in grid terms: format, a grid-derived logo size (`{ line: k }` or `{ span: n }`), named horizontal/vertical anchors, and the line kind. Choose a grid line and the logo's size and its line resolve. The caller never places pixels. |
| content | VIContent | – | Flat, serializable text slots (eyebrow, title, subtitle, meta): the exact shape a generated brief fills. Renders only alongside textLayout; a plain string per field, never rich text or a node. |
| textLayout | VITextLayout | – | Where the text sits on the grid: the block's start column, its measure-cap grid line, and vertical anchor, plus the footer meta row. Bounds are grid indices, not pixels. |
| media | VIMedia | – | Optional full-bleed image behind the skeleton. `src` is a URL string (never an element), so a CMS/brief supplies it; `tone: "dark"` flips every ink white; `placement` bottom-anchors a lower-half image. |
| resolved | ResolvedVILayout | – | A precomputed resolveVILayout(config) result. Pass it when an overlay needs the same geometry as the render (the rules page shares one resolve between the canvas and its annotations); omit and it resolves from config. |
| grid | boolean | false | Draws the construction grid overlay: margins, gutters/cells, and the mirror lines. A docs and gallery affordance, not for shipped artifacts. |
| className | string | – | Styles the canvas frame. The canvas is w-full with a fixed per-format aspect ratio, so size it by constraining the width; type is sized in cqw against it, so it scales at any display size. |