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/viLayout

Preview

How CEOs are rethinking growth in a slower market

Leaders are reallocating capital toward resilience.

alixpartners.com

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

01

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,
  }}
/>
02

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.

Northwind IndustriesJune 2026
<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" } }}
/>
03

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" }}
/>
04

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.

alixpartners.com/working-capital

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.

alixpartners.com

Disruption Index

Annual B2B growth webinar

Register now on alix.com
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

PropTypeDefaultDescription
configVIConfigThe 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.
contentVIContentFlat, 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.
textLayoutVITextLayoutWhere 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.
mediaVIMediaOptional 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.
resolvedResolvedVILayoutA 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.
gridbooleanfalseDraws the construction grid overlay: margins, gutters/cells, and the mirror lines. A docs and gallery affordance, not for shipped artifacts.
classNamestringStyles 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.

See also