---
title: "Data attributes"
description: "Every data-* attribute the system emits, the condition that produces it, and the values it can take."
url: "https://opsinjs.pensievelabs.org/reference/generated/data-attributes"
source: "https://opsinjs.pensievelabs.org/reference/generated/data-attributes.md"
section: "Reference"
kind: "reference"
reviewed: "2026-09-02"
reviewer: "engineering"
aliases: ["data-attributes", "styling hooks", "every data attribute"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="reference" />

## How this is generated [#how-this-is-generated]

**Source:** the component sources under `registry/bases/base` and the registry
items generated from them. &#x2A;*Script:** `scripts/build-registry.mts` produces the
data; `scripts/build-reference.mts` writes this page. &#x2A;*Command:**
`pnpm run generate`.

A data attribute is a styling contract, not an implementation detail. Everything
listed here is covered by the [versioning policy](../../project/versioning-policy.mdx):
removing an attribute, or changing the condition under which it appears, is a
breaking change and gets a major version, because somebody's stylesheet selects
on it.

**Four columns.** *Attribute* is the literal string you select on. *Emitted by*
is the component and the part. Attributes appear on parts, not on components as
a whole, and `data-open` on a dialog's backdrop is a different contract from
`data-open` on its panel. *Condition* is the state that produces it, written so
you can reproduce it. *Values* is the closed set, or `(none)` for a boolean
attribute whose presence is the signal.

**Two families appear here.** Attributes inherited from Base UI behave exactly as
Base UI documents them, and are listed with a one-line summary and a link rather
than re-documented. That family is `data-open`, `data-closed`,
`data-starting-style`, `data-ending-style`, `data-disabled` and their relatives.
Attributes opsinjs adds are documented in full, and are the ones carrying system
meaning: the clinical status of a value, the category it belongs to, whether the
data behind it is stale or partial.

**What is deliberately excluded.** ARIA attributes are not data attributes and
are documented per component under Accessibility. Attributes emitted by the
documentation site's own chrome are not part of the system.

**The table below is empty, and the components are not.** The implemented
components under `registry/bases/base` carry `data-slot` on every part, plus
`data-status`, `data-category` and `data-opsinjs-value` where
those apply, and they inherit Base UI's `data-open`, `data-starting-style` and
`data-ending-style`. What is missing is the extraction: the generator does not
read the component sources for `data-*` yet, and a table filled by guessing the
condition and the value set would be inventing a styling contract that consumers
then select on. Until it is written, read the attributes off the anatomy block on
each component's own page. Separately from the system,
`data-opsinjs-not-implemented` is emitted
by every not-built-yet marker on this site, so a program can tell a specification
from a component without parsing prose.

{/* opsinjs:generated:begin - everything below is replaced by scripts/build-reference.mts */}

<NoDataYet script="scripts/build-reference.mts" />

This script does not read the component sources for `data-*` attributes yet, so the table below has no rows, which is not the same as no attribute being emitted. The base layer under `registry/bases/base` emits `data-slot` on every part, plus opsinjs's `data-status`, `data-category` and `data-opsinjs-value`, and inherits Base UI's `data-open`, `data-starting-style` and `data-ending-style`. That is the whole component contract, and the Handbook states it is closed at those four. The squircle is not among them: Card, Callout and Dialog deliver it with an inline `corner-shape` property, `[corner-shape:var(--opsin-corner-shape)]`, rather than stamping a `data-opsin-shape` attribute, so no component emits one. `data-opsinjs-not-implemented` is not a component attribute either. It is a documentation-site marker that this site's not-built-yet chrome stamps so a program can tell a specification from a shipped component. Filling this table needs the attribute, the part, the condition and the value set together; the vocabulary itself is specified in the Handbook under Data attributes.

{/* opsinjs:generated:end */}
