Skip to main content
At the end of this page your organization will have a theme that every page uses, in light and dark mode, and you will know how agents put Bases records on a page with your organization’s widgets, how to make a widget, and what both do for accessibility.
Themes and widgets need ravi.bot@next and a Console login (ravi login). @latest has no ravi themes or ravi widgets command. Install @next with bun add -g ravi.bot@next, or switch an existing install with ravi update --next.

Concepts

Themes and widgets are Library items, like Library skills: they belong to the organization or to one project, have versions, and go through proposals and review.

Publish a theme

1

Start from the default theme

This writes theme.json and fields.json with Ravi’s default theme. The folder name becomes the theme’s slug unless you pass --slug when you publish.
2

Edit the tokens

theme.json uses the W3C Design Tokens format. Change what you need; anything you leave out keeps the default.
The theme covers background, text, border, focus ring, accent and the nine option colors (for each mode), fonts and sizes, radius, shadows, spacing and the board column width. Fonts are names of fonts already on the reader’s device: themes do not load web fonts. You can add fields.json (how each field type shows by default, such as status as a pill or as steps), logo.svg (at most 64 KiB) and a README.md.
3

Check it

The Console compiles the theme and measures contrast in light and dark mode. Each problem prints as file:line:column severity code message, and any error exits 2. A theme whose text and background colors fall below the minimum contrast cannot be published (see Accessibility).
4

Publish it

--scope org makes it available to every project; --scope <project> to one project. Without --scope, it goes to your default Console project.
5

Make it the active theme

Without --project, use sets the organization’s theme. With --project, it sets that project’s, which wins over the organization’s. none removes it.
Who can do what follows the Library’s rules: organization owners and admins publish organization themes and set the organization’s active theme, and project admins do the same for their project. Developers propose a change for review instead, and agents always do:
Agents can never publish a theme or set the active one (LIBRARY_AGENT_CANNOT_PUBLISH, LIBRARY_AGENT_CANNOT_SET_THEME, exit 3). Reviewers comment on and publish proposals in the Console Library.

When pages change

ravi pages ship puts the active theme into every HTML file of the page.
  • A private route shipped without --theme follows the active theme: the next time someone opens it, it picks up the current version.
  • Every other route (public, protected_link, password-protected, or shipped with --theme) keeps the theme it was shipped with until you ship it again. ship warns when a route’s theme changed since its last publish.
  • ravi pages ship --theme org/other uses another theme for that ship, and --theme none ships without a Library theme.
  • ravi pages outdated lists the routes shipped with an older theme, widget or page runtime. Ship them again to update them.
Hand-written pages follow the theme when their CSS uses its variables, such as color: var(--color-text-primary, #17202a) and background: var(--color-background-primary, #fbfcfd). The variables use the MCP Apps names (--color-*, --font-*, --border-radius-*, --shadow-*) plus Ravi’s own (--ravi-color-accent, --ravi-option-<color>-bg, --ravi-space-1 to --ravi-space-6). Dark mode follows the reader’s system setting; data-theme="dark" or "light" on <html> forces one. Agents learn this from the Pages skill.

How agents use widgets

An agent building a page over a Bases view looks for a widget, writes a tag, and ships:
  • <ravi-view> lists a view’s records as a list, board, table or gallery. Without widget, it uses the view’s default widget, or the default card.
  • <ravi-form> is a form that creates a record through the view.
  • <ravi-widget> draws one card from fixed values in data: the same numbers for everyone, never live rows.
When the agent ships, the Console applies the theme, checks each widget’s fields against the view’s columns, pins the widget versions and the page runtime, and adds the ravi.bases.* actions the tags need to the page’s --uses. A required field that matches no column, or a tag that is not closed, stops the ship (exit 2) with the file, line and column, and nothing is published. The tags follow the same rules as any data page: signed-in members only, on private or protected_link routes. To make a widget the default for every page over a view (board, list, gallery or calendar layouts):
bind applies at once and needs permission to manage the base. --field maps a widget field to a column when their names differ.

Make a widget

Make one when the same card keeps showing up across pages, or when you want a record to look a particular way everywhere.
A widget folder holds widget.json (name, description and typed fields), widget.html (the template), and optionally widget.js (see Widgets with code), example.json (made-up records for previews) and README.md. The template uses only Ravi’s building blocks, such as cards, rows, titles, fields, badges, images, links, metrics, progress bars and buttons:
Buttons can only update, create, archive or open the record, and they show only to people the view lets do that. ravi widgets catalog lists every building block and option. Agents propose widgets (ravi widgets propose ./order-card --scope org --execute); people publish them. An agent proposes to the organization or to its own Console project; when it names another project, the refusal (LIBRARY_OUT_OF_VIEW) says which organization and project the session sees. init --from-view reads the view in the session’s Console project; when the view is not found, the error names that project, and --project <slug> picks another.

Widgets with code

When a template cannot draw what you need, such as a chart or a map, a widget can add a widget.js file. The template stays required: it is what the ship draws into the HTML, and what agents and readers without JavaScript get. The script only enhances the card on top of it.
  • widget.js is one ES module of at most 128 KiB with its libraries bundled in. It cannot import anything, load remote scripts or start workers.
  • It exports mount(card, ctx) and returns { update, destroy }. ctx carries the card’s record and fields, canvas(i), the locale, time zone and light or dark mode, theme(name) for theme variables, announce(text) for screen readers, and onModeChange(fn).
  • The script draws only inside <ravi-canvas label="Sales by month">, the one free-drawing area, which needs a label and exists only in widgets with a widget.js.
  • Widgets with code are read-only for now: buttons in the template keep working, but the script cannot change records.
  • Only an organization owner or admin can publish a widget with code; agents and developers propose it. The Console runs an accessibility audit of the card with its script over example.json, and a page that uses a version whose audit has not passed does not ship: ravi pages ship exits 2 with KIT_WIDGET_UNAUDITED and the Console link where an admin runs the audit.
ravi widgets catalog marks these widgets “com código” (script: true in --json).

Accessibility

Accessibility is built into the building blocks and checked when a theme or widget is published, so a page does not depend on each author remembering it.

For people

  • A theme is refused when a text and background pair falls below 4.5:1, or a control border, focus ring or accent below 3:1, in light or dark mode, including each option color.
  • Every record is a titled card; status always shows its name, not just a color; buttons are real buttons named after the action and the record (“Mark as done: Order 1042”).
  • Everything works from the keyboard, with a visible focus ring in the theme’s color. On a board, cards move with a “Move to…” menu as well as by dragging, and Esc closes menus and dialogs.
  • Loading, saving, conflicts and errors are announced to screen readers.
  • Reduced motion, high-contrast (forced colors) mode, 200% zoom and 24 px touch targets are respected.

For agents

  • Page Chat, browser agents and ravi pages read read the same accessibility tree people’s screen readers do.
  • Every record carries data-ravi-row, and every field data-ravi-field and data-ravi-value with the raw value, so agents do not have to parse formatted text.
  • Cards with fixed values are drawn into the HTML when the page ships, so readers that do not run JavaScript still see them.
Publishing a widget fails when an image has no alt text, an avatar has no name, a button or link has no text, a color or tone is the only signal, a heading level is skipped, or a card has no title.

Limits worth knowing

An older Console without widget support ships the HTML unchanged with the warning KIT_UNAVAILABLE, and the tags do not render. Every other warning the Console sends on a ship (for example KIT_THEME_DEFAULT or KIT_ACTIONS_NEED_VIEW) is printed with its code and message and listed in warnings; warnings never stop the ship.