Skip to main content
Blocks are built from a fixed catalog of components. Reintersect draws each one with its own design system, so blocks match the product in light and dark mode, at every width and on every surface. Import components from @reintersect/sdk/components:
Set "jsx": "react-jsx" and "jsxImportSource": "@reintersect/sdk" in your tsconfig.json. There are no intrinsic elements: <div> and <span> are type errors. Your own function components may return JSX or a Promise of JSX. To write components that return an Effect, set "jsxImportSource": "@reintersect/sdk/effect" instead.

Groups

Frame

Block, Header, Section, Footer, Divider

Layout

Stack, Row, Columns, Column, Callout

Text

Text, Heading, Prose, Code, Quote, Time, Duration, Icon and rich text

Data

Badge, Status, Person, MetaList, Facts, Metrics, Progress, AvatarStack, ItemList, Steps, Table, Chart, Image, File, Reference

Interactive

Button, Actions, Menu, ChoiceGroup, Form, Toggle, Select, TextInput, DateTimePicker, Tabs

Recipes

Ready-made blocks built from the catalog: approvals, polls, deployments, incidents and more.

Every export

Code is a code block. Inline code is InlineCode. Links are the only underlined text.

What you describe

Props describe what something is: its structure, its numbers, its states and whether an action is destructive. There are no className, style, colour, pixel, size, gap or emphasis props. These values are shared across the catalog: Percentages are fractions: 0.08 is 8%. That applies to every format: "percent" value and to a metric delta with percent: true. Text takes muted and nothing else for colour. The only colour you can set is the swatch dot on a Badge (a #rrggbb string), for labels that have a colour in your own product, such as an issue label. Your app’s icon is the only brand mark a block shows.

What Reintersect decides

Reintersect decides how every block looks, the same way for every app, and no prop overrides it.

Anatomy

Every block has the same shape: a Header, then the body, then one Actions row, then a Footer. Each part is optional, but the order is fixed. The root Actions must be the last thing in the body. Forms end with their own submit row. There are no other places for an Actions row.

Spacing

Spacing comes from the relationship between siblings:
  • Closely related pairs, such as a Text followed by its MetaList, or two Progress bars one after the other, sit 8px apart.
  • Ordinary siblings sit 12px apart.
  • Groups (Section, Columns, Tabs, Form, Stack and Callout) get 24px around them, twice the ordinary gap.
A Divider at the start or end of a container, or next to another divider, isn’t drawn.

The primary button

The first button in the block’s root Actions that isn’t destructive is the primary button, drawn with a solid fill. Every other button is secondary. A destructive button is never primary: it’s outlined in the danger colour and always asks for confirmation. When a block has no root Actions and exactly one Form, that form’s submit button is primary instead. Buttons in a Row are small accessory buttons and never primary. Primary is worked out per viewer, from the buttons that viewer can see. See audience per button.

Tones

Five tones carry meaning, and each one pairs a colour with a glyph and a spoken word, so the meaning never rests on colour alone: Every tone’s text colour is at least 4.5:1 against the surfaces blocks sit on, in light and dark mode. A toned badge or callout always shows its tone’s glyph, so only neutral ones take an icon.

Wrapping and long text

Nothing scrolls sideways, and truncated text always has a way back to the full value:
  • Titles and labels wrap to two lines, then clamp.
  • Short labels (badges, statuses, people, buttons, tabs, menu items, options and column headers) show the full value in a tooltip on hover, focus and long-press.
  • Long text gets an inline Show more: descriptions and steps after three lines, table text cells after three, Quote after six, Prose after twelve, and Text after its maxLines.
  • Metric values never clamp. They shrink a step, then wrap.

Numbers

You pass numbers, and Reintersect formats them in the viewer’s locale with tabular figures. Values of a million or more are compacted, such as 1.2M, and the exact value stays in the tooltip and the accessible text. Deltas get their sign, arrow and colour from the number and goodWhen. Progress text comes from value, max and unit.

Narrow layouts

Blocks adapt to the width they’re given, from a phone to a wide thread:
  • A Table narrower than 400px becomes a list of cards. The first column is each card’s title, the other columns become label and value pairs, and the row menu sits top right. Sortable tables get a Sort menu.
  • Columns go side by side only when every column gets at least 200px, measured on the columns themselves. Otherwise they stack into one column; they never wrap as two plus one. Two columns sit side by side in a conversation thread, and three stack there.
  • Metrics pick their columns from how many there are, in balanced rows: four go two by two, five go three and two.
  • Lists below 320px move each item’s trailing content under its title, and fact labels above their values.
  • Tabs that don’t fit move into a trailing More menu. The selected tab stays visible.
  • A ChoiceGroup asks for an appearance. Segmented controls are used for 2 to 4 short options and chips for up to 8; anything else becomes a list.

Headings and time

Heading levels are relative to where the block sits. The header is one level, sections the next, and headings inside Prose fall below those, so a block never outranks the page around it. Relative times count calendar days in the viewer’s time zone, so 14 days ago reads “2 weeks ago” and a time from another day shows its date too.

Icons

Components with an icon prop accept a name from a fixed set of 153 Lucide icons, plus "app" for your app’s own icon. The type is a closed union (IconName), so editors complete the names and a typo is a type error. reintersect check also flags icon names the host no longer knows. Buttons, menus and menu items take an ActionIconName: every icon except loader, chevron-down, chevron-up, chevron-left, chevron-right and x, which Reintersect keeps for its own controls. ellipsis is allowed, since it’s the natural icon for an overflow menu.
app, activity, alarm-clock, archive, arrow-down, arrow-left, arrow-right, arrow-up, at-sign, award, ban, bell, bell-off, bold, book, bookmark, bot, box, briefcase, bug, building, calendar, calendar-check, calendar-clock, camera, chart-bar, chart-line, chart-pie, check, check-check, chevron-down, chevron-left, chevron-right, chevron-up, circle, circle-alert, circle-check, circle-dot, circle-help, circle-x, clipboard, clock, cloud, code, cog, command, copy, cpu, credit-card, database, dollar-sign, download, ellipsis, external-link, eye, eye-off, file, file-text, filter, flag, flame, folder, gauge, gavel, gift, git-branch, git-commit-horizontal, git-fork, git-merge, git-pull-request, git-pull-request-closed, git-pull-request-draft, globe, hammer, hash, heart, history, house, image, inbox, info, key, laptop, layers, layout-grid, life-buoy, lightbulb, link, list, list-checks, list-todo, loader, lock, lock-open, log-in, log-out, mail, map-pin, megaphone, message-circle, message-square, mic, milestone, minus, monitor, moon, package, paperclip, pause, pencil, phone, pin, play, plus, power, refresh-cw, rocket, rotate-ccw, save, search, send, server, settings, share, shield, shield-check, sparkles, square, star, sun, tag, target, terminal, thumbs-down, thumbs-up, ticket, timer, trash, trending-down, trending-up, triangle-alert, trophy, undo, upload, user, user-plus, users, video, wand, webhook, wifi, wrench, x, zap

Audience

Most container and data components accept an audience prop that limits who sees them: Inside the root Actions, each Button and Menu takes its own audience, so everyone can see Open while only admins see Merge. Viewers see only the buttons their audience allows, the primary button comes from those, and an Actions row whose buttons are all hidden from a viewer isn’t drawn for them. <Actions audience> hides the whole row instead. Reintersect enforces audience on the server, so hidden parts never reach a viewer’s browser. Audiences don’t nest, and nothing inside a Form may carry one. See Audience.

Nesting rules

The types enforce some nesting rules. JSX erases every element to the same type, so the types can’t express the rules below. The server enforces them, and reintersect check and the testing kit run the same validator, so a broken tree fails before you deploy:
  • Block is the root of every block view, and appears once. Its children go Header, body, Actions, Footer, in that order.
  • The root Actions is the last part of the body. A Form ends with its own submit row. Actions can’t go anywhere else: not in a Section, Tab, Column, Callout, Row or Stack.
  • Actions holds one to five Buttons, then at most one Menu (or just a Menu). Labels are unique across the row. Menu holds only MenuItem and MenuSeparator, at most 12 items with unique labels.
  • Sections sit at the block, tab or form level, one deep: a Section can’t hold another Section.
  • Columns holds two or three Columns, at the block, section, tab or form level. A Column can’t hold a Table, Chart, Prose, Heading or Code, because those need the full width.
  • Tabs holds two to six Tabs with unique labels, at the block or section level. Tabs don’t nest.
  • A Form can’t hold another Form, and a Stack can’t hold another Stack.
  • Callout holds only text: Text, Prose and MetaList. Put a callout’s buttons in the root Actions.
  • Row holds one or two Text or AvatarStack elements, then up to three Button or Menu accessories.
  • Footer holds one Text or one MetaList.
  • MetaList (up to 6 items), Facts, Metrics (up to 6), ItemList and Steps hold only their own item component. Fact and metric labels are unique within their list.
  • Toggle, TextInput, DateTimePicker and Select take a field from a Form render prop, so they only appear inside a Form.
The full list of size limits and the messages the validator reports are on Limits.

Not in the catalog

The catalog leaves these out on purpose, so that every block stays readable and accessible and blocks look consistent with each other:
  • Custom colours, fonts, sizes, spacing, SVG and CSS.
  • Free-form grids, spacers and absolute positioning.
  • Disabled controls. Hide what doesn’t apply; Reintersect dims controls only when a member can’t act, and always says why.
  • Blocks nested inside blocks, and tabs inside tabs.
  • Animation control.
  • Raw HTML and iframes. The only embeds are the providers Reintersect already supports, such as YouTube, which you place with a Reference to the URL.
  • Dialogs or pages opened by your app. Use a section with a form instead.

How each component reads as text

Reintersect projects every stored tree to plain text for agents, notifications and search. Each component page says what that component adds to the text. These rules apply across the catalog:
  • Containers join their children’s text. Section, Callout, Tabs and ChoiceGroup put their title or label on the first line.
  • Buttons and menu items read as [Label]; link buttons read as [Label](https://…).
  • Fact reads as Label: value, Item as a - list line, and MetaList joins items with ·.
  • Numbers read in a stable en-US format, such as Error rate: 0.4%.
  • Anything with an audience is left out of the shared text. Members who pass the audience get it merged into their own projection.
  • Decorative images are left out; other images read as [Image: alt].