# CSSV · Comma-Separated Styled Values

> Source: <https://cssv.dev/>
> Published: 2026-10-06 18:44:30+00:00

### Plain text

Data and styles are text you can read, diff, review and merge. A change to one number is a one-line diff.

CSSV v1 · Comma-Separated Styled Values

CSSV is a CSV file with a CSS stylesheet on top. One file holds the data and how it looks, renders as a real HTML table, and stays plain text you can read, diff and generate. Delete the stylesheet and it's a CSV again.

Exported with `toMarkdown()` from computed styles: alignment, bold, italic, strikethrough and monospace carry over.

Change a delay: `0` is on time, `25` is delayed, `-5` is early. Delete one to cancel the flight.

**Ask your AI agent to write it**

Example prompt

“Turn these flights into a departures board using CSSV. Show delayed flights in amber and cancelled flights in red.”

Examples

Each of these is one `.cssv` file rendered by the same element. The page adds no styles to them: the element tiles, arrows, letterhead and crests all come from the file's own style block.

118 records in atomic-number order. The style block makes `tbody` an 18-column grid, so `tr:nth-child(26)` is iron: most elements flow into place, and five rules move the rest. `--cssv-key: category` names every row by its category, and each category sets one color.

Every number cell is tagged `.positive`, `.negative` or `.zero`. That drives the arrows, the colors, the pill tints and the row highlights, and no value is repeated in the CSS.

SKUs like `0042` stay text because of the leading zero. Quoted fields span two lines and `::first-line` bolds the title. Summary rows have no SKU, so `tr:not([data-key])` selects them.

Semicolon-delimited, detected from the header. Ranks come from a CSS counter, crests from two colors per club, and zones from `:nth-child`. The palette follows `prefers-color-scheme`.

[Open the full gallery](../examples/): CSV files people already have, from a Jira export to a WhatsApp chat, more files like these, and every file's source.

Why CSSV

CSV has the data but nothing about how it looks. Spreadsheets have both, in a binary file you can't diff, review or write as text. HTML tables wrap every value in markup. Markdown tables are text but stop at bold and alignment. CSSV keeps the data as CSV and puts the presentation in CSS, two languages that people, programs and language models already write.

Data is RFC 4180 CSV and styles are CSS. CSSV only defines how they connect: two fences and a fixed table structure.

The output is an ordinary `<table>` that browsers lay out, select, copy, print and read aloud without plugins.

Many files can `@import` one stylesheet, so changing it restyles every report at once.

Each value appears once in the file, and each style once per rule rather than once per cell. That keeps files short to write by hand and cheap to generate.

Delete the lines between the fences and you have a CSV file. Most CSV files are already valid CSSV files.

How it works

Two `---` fences split the file: CSS above, CSV below. The renderer turns the CSV into an HTML table whose structure the spec fixes, then applies the CSS to it. Because every renderer builds the same structure, a stylesheet written for one works in all of them.

`.cssv` file
A style block between fences, then CSV. This one imports a shared `brand.css`.

```
Loading…
```

Column names go in `data-col`, record numbers in `data-row` and the key column's values in `data-key`. Four classes mark numbers and their sign. Nothing else is added, and no other cell value is copied into an attribute.

```
Loading…
```

Numbers are formatted for the reader's locale. Hover or tap a cell to see the selectors that reach it.

`data-col`
Every `col`, `th` and `td` carries its column name, exactly as written.

`[data-col="diff"]`` data-row`
Every row carries its record number. The header is row 1.

`tr[data-row="4"]`` data-key`
Pick a key column and style rows by name, wherever they sit in the file.

`tr[data-key="Total"]``.number`
Numbers align to the end and line up their digits. Number columns get it on the header too.

`.negative .zero .positive`
The renderer signs every number, so colors and arrows need no values in the CSS.

`[data-col="diff"].negative``--cssv-key`
Choose the key column from the stylesheet, so the file renders the same in every host.

`table { --cssv-key: category; }``--cssv-format`
`Intl.NumberFormat` options per table, column, row or cell. It inherits like any custom property.

`--cssv-format: "maximumFractionDigits: 0"`
`:has()`, `:nth-child`, counters, `::before`, `color-mix()`, `@media print`, `prefers-color-scheme`, `@import`, cascade layers… the style block is a normal stylesheet.

Localization

Files always store numbers the same way: `1234.5`, whatever the delimiter. The renderer formats them for the reader's language, and `--cssv-format` picks decimals and grouping. Values are exact and never pass through floating point, so `9007199254740993`, one more than a JavaScript number can hold exactly, keeps every digit.

Get started

The renderer is a custom element, `<cssv-table>`, in two small ES modules with no dependencies. The parser also runs in Node, without a DOM.

From npm, a CDN, or by copying `src/cssv-table.js` and `src/core.js` next to each other.

```
npm install @rhpaiva/cssv
// Defines <cssv-table> in the browser
import '@rhpaiva/cssv/cssv-table.js';

// The DOM-free processor, for Node or the browser
import { parse, toHtml } from '@rhpaiva/cssv';
<script type="module"
  src="https://cdn.jsdelivr.net/npm/@rhpaiva/cssv@0.2/src/cssv-table.js"></script>
php
<!-- core.js must sit next to cssv-table.js -->
<script type="module" src="https://cssv.dev/js/cssv-table.js"></script>
```

Start from the CSV you have and put a style block above it. The styles travel with the file, so every page that renders it gets them.

``` python
---
@import url("brand.css");

table { --cssv-key: item; }
.negative { color: crimson; }
tr[data-key="Total"] { font-weight: bold; }
---
item,amount
Rent,1200
Refund,-45.50
Total,1154.50
```

Point `src` at a file, or write the file inline in a `<script type="text/cssv">`, which the browser neither runs nor parses as HTML.

``` php
<cssv-table src="budget.cssv"></cssv-table>

<!-- Inline -->
<cssv-table>
  <script type="text/cssv">
    ---
    .negative { color: crimson; }
    ---
    item,amount
    Refund,-45.50
  </script>
</cssv-table>
```

Serve `.cssv` files as `text/plain; charset=utf-8`. Wide tables overflow the element; add `cssv-table { overflow-x: auto; }` to scroll them, and style the table from the page with `cssv-table::part(table)`.

`core.js` has no DOM dependencies. `toHtml()` produces the table model for server-side rendering or static output, in the locale you choose.

``` js
import { readFile } from 'node:fs/promises';
import { parse, toHtml } from '@rhpaiva/cssv';

const model = parse(await readFile('budget.cssv', 'utf8'));
// { style, delimiter, columns, numberColumns, rows }

const html = toHtml(model, { locale: 'de-DE', key: 'item' });
// <table><colgroup>…</colgroup><thead>…</thead><tbody>…</tbody></table>
```

For AI agents

Language models already write CSV and CSS. The CSSV skill for Claude Code adds what they can't guess: the fences, the number rules, the table model's hooks, the two CSSV properties and the usual mistakes. Claude loads it when you mention CSSV or ask for a styled table kept as a plain-text file.

One Markdown file, `SKILL.md`, with a short header Claude reads to decide when to use it.

[Download SKILL.md](../skills/cssv/SKILL.md) 

In your home folder for every project, as below, or in `.claude/skills/cssv/` inside one project to share it with your team. Claude Code picks it up without a restart, and `/skills` lists it.

```
curl --create-dirs -o ~/.claude/skills/cssv/SKILL.md https://cssv.dev/skills/cssv/SKILL.md
```

Claude writes a `.cssv` file, goes through the skill's checklist and, when Node is available, runs the skill's check script before it finishes.

`sales.csv` into a CSSV report: losses in red, the total row in bold.

```
Loading…
```

The skill uses only the standard Agent Skills header fields (`name`, `description`, `license`), so the same file can also be added to Claude.ai or used with the Claude API.

To write a styled table in HTML, a model wraps every value in markup, formats every number by hand and adds a class to every cell that needs one. In CSSV it writes the data once plus a few rules, and the renderer does the rest. For data-heavy tables that's a third to half the tokens, and the gap grows with the rows. For a small table with a lot of styling, the CSS is most of the file either way, and the saving drops to 10–25%.

Open specification

CSSV v1 is open for review. The spec fixes the file format, the table model and how renderers apply styles, so other implementations can render the same files the same way.

Median times for generated 6-column tables in headless Chromium, with a style block, `--cssv-key` and `--cssv-format`. Parsing is a small share; most of the time is the browser creating cells and computing their styles, which grows linearly.

Probably not. A CSV parser and a few lines that build a table will do, and your page's CSS can style it. CSSV is for when the styles belong to the file: a report that a script or a language model writes complete with its look, or one file shown on several pages that should bring its styles along. `<cssv-table>` still renders a plain CSV file with neutral defaults, so you can start from an export and add a style block later.

You can, and CSSV produces one. Written by hand, though, every value sits inside markup, and the attributes you style by have to be kept in sync with the data. With CSSV the data stays CSV, so any spreadsheet export or CSV library can produce it, and the renderer adds the hooks.

A file without a style block is a plain CSV file and opens anywhere. A file with one shows its CSS lines as rows in a spreadsheet; delete the lines between the fences and it's CSV again.

A CSSV file can't run scripts, and cell values are always inserted as text. The renderer isolates each table in shadow DOM and contains its painting to the table's box. A stylesheet can still load remote resources and change what the reader sees, so treat an untrusted file like a stylesheet from an untrusted page. The spec's section 11 covers this.

It's CSV and CSS, which models already write well, so there's no new syntax to learn, and a data-heavy table takes a third to half the tokens of the same table in HTML. The [CSSV skill](#ai) teaches Claude the rest.

Formulas, merged cells, multiple sheets and editing are out of scope. Dates, comma decimals, percent and currency formats, files without a header and XLSX export are candidates for later versions.

It uses web components, shadow DOM, cascade layers and `Intl.NumberFormat`. The test suite runs in Chromium, and the site has been checked by hand in Chrome and Firefox. Safari isn't tested yet.
