opsinjs
HandbookWorking with components

Data attributes

The shared state-attribute vocabulary. Where it comes from, which attributes are Base UI's and which are opsinjs's, and why it is a versioned contract.

The short version

NOT IMPLEMENTED. This component does not exist in any released version of opsinjs. There is no package to install, no module to import and no props interface to generate code against. Everything on this page is a specification of intended behaviour and may change without notice. Do not write code against it.

This component is not built yet

There is nothing to render because there is nothing to install. What you can read on this page is the specification the implementation will have to satisfy.

PlannedRoadmapWhat “planned” means

Every stateful part will publish its state to the DOM as a data-* attribute, so styling a state needs no JavaScript:

[data-open] { /* the popup is open */ }
[data-status="urgent"] { /* the value's clinical status */ }
[data-category="sleep"] { /* which metric family this belongs to */ }

Two families of attributes exist and they come from different places. Base UI publishes the interaction state: open, closed, disabled, and the animation lifecycle. opsinjs publishes the semantic state: clinical status and category. The Reference → Data attributes table has no extracted rows yet, so the vocabulary you can rely on is the one on this page.

These attributes are covered by semver. Renaming one is a breaking change, exactly like renaming a prop. That is a deliberate commitment; see Versioning policy.

How it works

Base UI's attributes

opsinjs is built on Base UI, so its state contract passes straight through. The ones you will use constantly:

AttributePresent when
data-openThe part is open
data-closedThe part is closed
data-disabledThe part is disabled
data-starting-styleFor one frame as the part enters
data-ending-styleWhile the part is leaving, before it unmounts

data-starting-style and data-ending-style are what make enter and exit transitions work without an animation library. You style the from-state and the to-state, and the element stays mounted long enough for the exit to run. See Motion in practice.

opsinjs's attributes

These carry meaning rather than interaction, and they are the reason this page matters more here than it would in a general-purpose library.

AttributeValuesWhat it means
data-slotpresent, naming the partOn every part of every component. This is the stable styling and testing handle, the one attribute you can rely on being everywhere.
data-statussteady · watch · attention · urgentThe clinical status of the value this part represents
data-categorythe metric family, e.g. heart · sleepWhich family the surface belongs to
data-opsinjs-valuethe unrounded magnitudeThe machine-readable number behind a Value. A magnitude is not a reading. Nothing beside it names the unit, so read the unit from wherever the caller set it, and never export or print this attribute on its own.

The component contract is closed at four: data-slot, data-status, data-opsinjs-value and data-category. That is the same four the component pages name, and no component adds a fifth. In particular there is no data-opsin-shape. Card, Callout and Dialog draw the squircle with the CSS corner-shape property, [corner-shape:var(--opsin-corner-shape)], and the product theme does not stamp an attribute for it either, so the shape is a token-driven switch you cannot select on.

data-opsinjs-not-implemented is not part of that contract. This documentation site stamps it on its own not-built-yet markers, NotBuiltYet and StubNotice in components/docs/stub.tsx, and on ComponentInstall in components/docs/source.tsx when the component it installs is unbuilt, so tooling can tell a specification from a shipped component without parsing prose. No component emits it, and a consumer's installed DOM will never carry it.

The five data states that Data states defines have no attribute today. There is no data-state in the DOM, so a surface's state is legible only from the words it shows. Do not write CSS or a test against a data-state selector: it would match nothing and fail silently.

data-status and data-category never appear on the same element with overlapping visual effect. That is the two-axis rule expressed in the DOM, and it is what the stylelint plugin checks.

Do this

  • Style states with attribute selectors rather than toggling classes. The component's DOM is already the source of truth.
  • Use data-status to drive presentation, never to compute one. The status is assigned upstream by whoever owns the thresholds; the attribute reports it.
  • Assert on data attributes in tests. [data-open] is a stable, documented contract; a generated class name is not. See Testing.
  • Read data-opsinjs-not-implemented if you are writing tooling. It is the machine-readable way to ask "does this exist yet" without parsing prose.

Not this

  • Do not set data-status yourself to force a colour. If you want a surface to look urgent without a value being urgent, you want a different component. Forcing the attribute puts a clinical claim in the DOM that nothing backs.
  • Do not invent attributes in the data-opsinjs-* namespace. It is versioned; your additions will collide. Use your own prefix.
  • Do not rely on attribute order or on the absence of one you have not read about. data-closed being absent is not a promise that data-open is present.
  • Do not use data-category for theming an unrelated surface. A settings page tinted with the sleep category colour teaches the reader that the colour means nothing.

Gotchas

  • Boolean-style attributes are present or absent, not "true" or "false". [data-open] matches; [data-open="true"] will not. Base UI renders these as empty attributes.
  • data-starting-style lasts one frame. You cannot inspect it comfortably in devtools, which is why enter transitions look "broken" when they are actually working. See Motion in practice.
  • data-ending-style requires the element to still be mounted. If you unmount on close yourself, the exit transition never runs and no error is reported.
  • Attribute selectors and Tailwind arbitrary variants disagree about escaping. data-[status=urgent]:bg-… works; quoting inside the arbitrary variant frequently does not.
  • Server-rendered markup carries these attributes too, which is what makes them safe to style with pure CSS. It also means, though, that an incorrect status is visible in view-source. Do not put anything in an attribute you would not put on screen.
  • Styling has the four hooks, of which this is the second.
  • Reference → Data attributes will hold the generated list once the extractor reads the component sources. Its table is empty for now, so this page is the vocabulary.
  • Clinical status semantics says what each data-status value is allowed to mean.
  • Data states has the five states the design contract names and what each obliges the surface to show. They are a design contract, not a DOM attribute today.

On this page