TypeScript
The exported surface comprises named prop interfaces, the ClinicalStatus and HealthCategory unions, and the rule that makes generated API tables possible.
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
PlannedRoadmapWhat “planned” means
Two rules define the TypeScript surface, and the second one is load-bearing for the documentation site itself:
- The semantic vocabularies are exported unions, not strings.
ClinicalStatusandHealthCategoryare the type-level form of the two colour axes, and using them means a mixed axis is a compile error rather than a review comment. - Every component exports a named props interface. Not an inline object
type, not a bare
ComponentProps<typeof Base.Root>. This is whatfumadocs-typescriptreads to generate the API table on each component page, so a component without a named interface has no API documentation at all.
How it works
The semantic unions
export type ClinicalStatus = "steady" | "watch" | "attention" | "urgent"
export type HealthCategory =
| "sleep" | "heart" | "activity" | "nutrition" | "mind" | "labs"These are ordinal in the first case and unordered in the second, and they are deliberately not merged into one "variant" prop. A component that accepts both accepts them as two props, which is how the never-mix rule survives contact with autocomplete.
Named props interfaces
export interface ResultCardProps extends React.ComponentPropsWithRef<"div"> {
/** The clinical status of the value being shown. */
status?: ClinicalStatus
/** The metric family this result belongs to. */
category?: HealthCategory
/** @internal */
debugSlot?: React.ReactNode
}What the generator does with it: reads the interface by name, renders each
member with its JSDoc as the description, surfaces defaults, marks required
members, links every referenced type name into
Reference → Types, and suppresses anything
tagged @internal. Props inherited unchanged from Base UI are delegated with
one sentence and a link rather than re-documented. opsinjs documents only what
it adds.
The consequence is that JSDoc is documentation, not a comment. A prop with no doc comment appears in the table with an empty description, permanently, in public.
Configuration
strict: true is on. allowImportingTsExtensions: true is set because the
.mts build scripts import lib/color/*.ts with the explicit extension Node
requires; noEmit: true makes that legal. noUncheckedIndexedAccess is
deliberately off. See the note in tsconfig.base.json.
Do this
- Name the interface
<Component>Propsand export it. The generator looks it up by that name. - Write a JSDoc line for every public prop, in the same voice as the rest of the documentation: what it does, not what it is.
- Use
@internalfor anything you do not want documented, rather than omitting the comment and hoping. - Extend
React.ComponentPropsWithRef<"element">so consumers get the native props and the ref type without your enumerating them. - Type your own wrappers from the exported interface, not by copying the
props.
Pick,Omitand intersection keep you honest across upgrades. - Import types with
import typeso they are erased and cannot accidentally create a runtime dependency.
Not this
- Do not use an inline object type for props. The generator cannot resolve it, so the component ships with an empty API table and nobody notices until a reader asks.
- Do not re-export
ComponentProps<typeof Base.Root>as your props type. It produces a table of Base UI internals and hides the props you added. - Do not widen
statustostring. It removes the only compile-time protection the two-axis rule has. - Do not use an
enum. The build scripts are erasable-syntax-only TypeScript run by plainnode; enums are not erasable and will fail at runtime with an error that does not mention enums. - Do not export a type only from a
.d.ts. The generator reads the source.
Gotchas
- A renamed interface silently empties the API table. No build error. The
generator finds nothing and renders nothing.
assert-iais the backstop. ComponentPropsandComponentPropsWithRefdiffer in React 19 in ways that matter forreftyping in arenderprop; picking the wrong one produces an error at the call site rather than at the definition.allowImportingTsExtensionsneedsnoEmit. Without it,tscfails with TS5097 pointing at the import rather than at the configuration..mtsfiles run bynodemust be erasable-syntax-only. No enums, no parameter properties, no namespaces. The failure is a runtime syntax error at the first use.- Generic components lose their generic through a naive
forwardRefwrapper. The type becomesunknownand every call site widens. - A type-only circular import type-checks but breaks the generator, which resolves modules eagerly.
Related
- Reference → Types is the generated list of every exported symbol.
- Naming conventions says what to call the interface, the props and the files.
- Code style has the named-props-interface rule as a contribution requirement.
- Clinical status semantics says what
each
ClinicalStatusmember is allowed to mean.
Internationalisation
RTL, locale formatting, and the hard part. That part is translating a status vocabulary without changing what it means clinically.
Naming conventions
The published naming contract for component ids, prop names, token names, data attributes, files and CSS custom properties, and where the two spellings diverge.