# Data Flow

> The JSON-to-custom-element rendering pipeline used for data tables and list-rendered content. Defines toTagName key conversion, the lifecycle from fetch to DOM, and the strict scope (data tables only — other features have their own patterns). Use when rendering tabular or list data from JSON, never for layout, never for static content.

- Skill: `autocss-com/data-flow` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add autocss-com/data-flow`
- Raw SKILL.md: https://api.skillmd.com/api/skills/autocss-com/data-flow/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- License: MIT
- Author: Autocss-com (https://skillmd.com/u/autocss-com)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/autocss-com/data-flow

---


# Data Flow

The pipeline that turns a JSON object into a populated set of DOM elements. Used for **data tables and list-rendered content only**. Other features have their own patterns and do not use this pipeline.

## Scope

This pipeline applies to:

- Data tables rendered as `<ul>` / `<li>` rows
- Form fieldsets populated from an API response
- Any structurally repeating content driven by an API

This pipeline does not apply to:

- Static layout regions (HTML provides them, CSS styles them)
- Theme tokens (CSS owns)
- Navigation behavior (CSS state machines own)
- Any feature with its own pattern documented in its own skill

If a feature has its own pattern in its own skill, that pattern wins.

## Why `<ul>` / `<li>` for data tables, not `<table>`

Data tables are a primary surface in this architecture — government work especially, where users interact with tabular data daily and have strong preferences about how they see it. The choice of `<ul>` / `<li>` over `<table>` is deliberate and produces several wins at once.

**Accessibility for keyboard and screen readers.** `<ul>` / `<li>` markup is intrinsically navigable by keyboard and read clearly by screen readers without needing role overrides, ARIA scaffolding, or accessibility plugins. `<table>` markup, by contrast, often loses screen-reader accessibility once styling, sticky headers, virtualization, or interactive cells are added — recovering it requires layered ARIA and JS.

**Semantically meaningful parent/child relationships.** A list of rows is, semantically, a list. The data is a collection of items; each item is a record. `<ul>` / `<li>` says exactly that. `<table>` implies a two-dimensional layout — rows × columns as a presentational grid — which couples the data shape to a single rendering. Using a list keeps the data's nature separate from how it's currently displayed.

**Multiple view modes from the same markup.** Because the markup is a list of items, CSS can render it in different shapes without changing the DOM:

- **List view** — one row per visible line, columns aligned via subgrid
- **Card view** — each `<li>` displayed as a card, columns laid out per-card
- **Other views** as needed (gallery, compact, dense, expanded)

The user picks the view; CSS reacts. No DOM rebuild, no JS template swap. `<table>` markup cannot do this without restructuring the DOM.

**End-user customization is a first-class design priority.** Users have spent careers looking at their data. They have preferences. They earn the right to see it the way they want. The architecture treats this as a feature, not an edge case — which means the markup needs to support it intrinsically. `<ul>` / `<li>` does. `<table>` does not.

**CSS subgrid for column alignment.** Now cross-browser baseline, `subgrid` lets each `<li>` inherit its parent `<ul>`'s column tracks so values align across rows the way table columns do — without using `<table>`. The list keeps its semantic and accessibility advantages; the columns line up exactly. (Pattern in `references/lifecycle.md`; full-column subgrid example to be added once the surface is researched and validated.)

**Future zen-mode (full-screen) view.** A planned, consistent feature across the architecture: any section the user is working in can be expanded to full-screen — not as a modal overlay but as a true zen view of that section. `<ul>` / `<li>` data tables are amenable to this without restructuring; they expand and reflow naturally inside the new viewport. (When implemented, lives under `css/references/`.)

## The pipeline

```
1. fetch JSON          (api.js)
2. parse to object     (api.js)
3. for each row:       (oninput.js)
   a. for each key in row:
      - tag = toTagName(key)
      - element = document.createElement(tag)
      - element.textContent = row[key]
      - row container appends element
4. row container appends to <ul>
5. CSS reacts via :empty / :has() / :not(:empty)
```

Step 3 is the only DOM mutation. It is idempotent: the row container is cleared via `clearFieldset` before re-render.

## `toTagName(key)` — the key-to-tag rule

A JSON key becomes a custom element tag name via `toTagName()`. The function:

1. Splits the key on camelCase boundaries (`productName` → `product`, `Name`).
2. Lowercases each part.
3. Joins with hyphens (`product-name`).
4. Prepends a namespace if none exists, to satisfy the custom-element hyphen requirement (`row-product-name` if the key is a single word).
5. Returns the tag name.

Examples:

| JSON key | Tag |
|---|---|
| `productName` | `<product-name>` |
| `unitPrice` | `<unit-price>` |
| `id` | `<row-id>` (single-word keys get a `row-` prefix) |
| `lastModified` | `<last-modified>` |

Custom element tag names always contain a hyphen — this is an HTML spec requirement, not a stylistic choice.

The function is named `toTagName`, not `toKebab`. The name describes the concern (producing a valid tag name), not the implementation (kebab-case).

## Schema-driven visibility and ordering

The order of keys in JSON does not determine the DOM render order. The order comes from a schema (external `.json` file) that lists the permitted keys for a payload type along with their preferred order.

```
schema example
[
  "id",
  "productName",
  "unitPrice",
  "lastModified"
]
```

Visibility is not a flag in the schema. Visibility is the data presence — an empty value renders an empty element which CSS hides via `:empty`.

## Positional mapping for page-level fields

Page-level fields map to canonical semantic positions, not to custom elements:

| JSON key | DOM target |
|---|---|
| `pageTitle` | `main article h1` |
| `intro` | `main article p:nth-of-type(1)` |
| `body` | `main article section` |

This mapping is fixed by the architecture and lives in `oninput.js`. New page-level fields require updating both the schema and the mapping.

## Card pattern reminder

Cards exist only in plural as `ul` / `li`. Each card row is a `<li>` whose children are `toTagName`-generated custom elements carrying the per-row data. A single, identity-bearing item uses `<article>` instead.

## What `data-flow` never does

- Never injects HTML strings (`innerHTML = '...'`)
- Never reads `class`, `id`, or `data-*` (those don't exist)
- Never writes inline styles
- Never sets visibility via JS (CSS does that)
- Never re-renders the shell (shell fetched once per session)

## Baseline & support

_Checked against MDN as of 2026-07-16._

- `custom elements` — **Baseline Widely available** — https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements
- `subgrid` — **Baseline Widely available** — https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout/Subgrid
- `:has()` — **Baseline Widely available** — https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:has

**Canonical rules:** https://github.com/Autocss-com/ai/blob/main/AGENTS.md

## References

- `references/totagname.md` — the function specification with edge cases (single words, all-caps, numerals)
- `references/lifecycle.md` — fetch → parse → render sequence in detail
- `references/schema.md` — schema file format and where it lives
- `references/positional-mapping.md` — page-level field-to-element table
- Custom elements spec: https://html.spec.whatwg.org/multipage/custom-elements.html

