Vitral 0.2
AI

Coding agents

Every page of this site is also plain Markdown, and the whole of it is one file, so an assistant can read the documentation instead of guessing at it.

An assistant that has not read the documentation writes what it remembers of some other library: utility classes over the components, a stylesheet fighting the theme, icon imports that do not exist. None of that is a hard problem — it is a reading problem. So the build writes this site twice: once as the pages you are looking at, and once as Markdown, in the shape llms.txt describes.

What is published

on every build
/vitral/llms.txt         the index: every page, with a line on what it covers
/vitral/llms-full.txt    all of it in one file
/vitral/docs/theming.md  any page, with .md instead of the trailing slash
/vitral/components/select.md

llms.txt is the index: the guides, every component and every template, each with the line that describes it, linking to the Markdown rather than the page. llms-full.txt is the lot, concatenated, for a model with the context to hold it. And any page answers with .md in place of its trailing slash, which is the cheapest thing to hand an agent that only needs one component.

The Markdown is generated from the rendered page, so it carries the prose, the headings and the API tables — which are themselves read out of each component's types.ts at build time. A component's page also gets the source of its examples appended, because on the site those sit behind a button and an agent cannot press it.

Point an agent at it

The whole of it is one line in a prompt:

a prompt
Read https://vitralui.github.io/vitral/components/datagrid.md
and write me a table of invoices with sorting, filtering and paging.

For a project you work in every day, put the rules where the agent reads them — AGENTS.md, CLAUDE.md, .cursor/rules, or whatever your tool calls it. This is the short version worth pasting, and it is about the things a model gets wrong from habit rather than from ignorance:

AGENTS.md
# Vitral

Docs: https://vitralui.github.io/vitral/llms.txt

- Import components from `@vitral/vue`: `import { Button, Select } from '@vitral/vue'`.
  In Nuxt they are global (`<VtButton>`) through `@vitral/nuxt`.
- Do not write utility classes and do not hand-write CSS to restyle a component.
  The look is tokens: `dt('button.background')`, a preset, or the `dt` prop.
- To change one instance's markup or classes, use pass-through (`pt`), not a wrapper.
- `unstyled` drops every class and keeps the behaviour and the ARIA.
- Icons are names resolved from `@vitral/icons`: `<Button icon="search" />`.
  Anything outside the base set has to be registered first.
- Fields are real controls: `<label for>`, `name` and `aria-describedby` reach
  them without extra props.

Why those rules

  • Tokens, not CSS. The look of every component is custom properties, which a preset sets and the dt prop overrides for one instance. A stylesheet written beside it works until the scheme changes.
  • Pass-through for markup. Every internal element is addressable by name, so an agent never has to wrap a component to reach inside it.
  • Icons are registered names. The components resolve the set they use themselves; anything else is a named import, or one registerIcons call. A made-up name renders as a class, silently.
  • The accessibility is already there. Each component follows its WAI-ARIA APG pattern and is tested against it, so added role and aria-* attributes usually break it rather than help.

Keeping it honest

Nothing here is written by hand, which is the point: the Markdown is the site, the API tables are the source, and both are rebuilt on every push. A page that goes out of date goes out of date in one place.