Fast React component prop extraction. Parses TypeScript with OXC, no type-checking pass.
caliper-docgen reads your components and reports each one's props: type, required or optional, default, and JSDoc. It is a faster alternative to react-docgen-typescript for Storybook, docs sites, and LLM tooling.
react-docgen-typescript builds a full TypeScript Program, which takes seconds on a mid-size design system. Caliper parses files in parallel with OXC and skips the type checker.
On whole libraries (Mantine, Chakra, Ant Design, shadcn/ui and React Spectrum at pinned commits, source only), Caliper takes 34 to 81ms and is 17 to 39 times faster than react-docgen-typescript on the same files, a median of 22 times. CI checks this weekly (docs/performance.md). Recall and agreement with react-docgen-typescript on installed repos are in docs/measurement.md.
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
/** Visual style. */
variant?: 'default' | 'destructive' | 'outline'
loading?: boolean
}
export function Button({ variant = 'default', loading, ...rest }: ButtonProps) {
return <button data-variant={variant} aria-busy={loading} {...rest} />
}caliper-docgen extract --src src --format rdt| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
variant |
"default" | "destructive" | "outline" |
no | "default" |
Visual style. |
loading |
boolean |
no |
By default all of an element's inherited HTML attributes are exposed (what react-docgen-typescript does); --html-attributes curated keeps a compact subset, none none. --format canonical is the tool's own shape, rdt matches react-docgen-typescript, and toon is a compact format for LLM agents.
%%{init: {'theme': 'base', 'flowchart': {'curve': 'basis', 'nodeSpacing': 40, 'rankSpacing': 56}, 'themeVariables': {'fontFamily': 'Inter, ui-sans-serif, system-ui, sans-serif', 'fontSize': '14px', 'lineColor': '#94a3b8', 'edgeLabelBackground': '#ffffff'}}}%%
flowchart LR
classDef source fill:#eef2ff,stroke:#6366f1,stroke-width:1.5px,color:#1e1b4b,rx:10px,ry:10px,font-weight:600;
classDef engine fill:#f5f3ff,stroke:#8b5cf6,stroke-width:1.5px,color:#4c1d95,rx:10px,ry:10px;
classDef output fill:#ecfdf5,stroke:#10b981,stroke-width:1.5px,color:#064e3b,rx:10px,ry:10px,font-weight:600;
files["<b>.tsx / .d.ts</b><br/>your source"] --> parse["<b>Parse</b><br/>OXC, in parallel"]
parse --> merge["<b>Merge</b><br/>symbols across files"]
merge --> resolve["<b>Resolve</b><br/>props per component"]
resolve --> out["<b>Output</b><br/>canonical · rdt · toon"]
class files source
class parse,merge,resolve engine
class out output
Caliper is pre-v1. docs/STATUS.md lists what works, and docs/edge-cases.md the known gaps. Conditional types and mapped types over an unbound generic need a real type checker, which OXC deliberately isn't.
npm install --save-dev caliper-docgen # CLI, with the prebuilt binary for your platform
npm install --save-dev caliper-docgen-vite-plugin # Vite plugin
npm install --save-dev caliper-docgen-storybook # Storybook 11 addon
cargo install caliper-docgen # CLI from crates.ioComing from react-docgen-typescript? MIGRATING.md covers what's compatible today.
# Extract every component under src/ to a file
caliper-docgen extract --src src --out docgen.json
# Props of one component, as a table
caliper-docgen inspect Button --src src
# Re-extract on every change
caliper-docgen watch --src src
# Fail CI on extraction errors (--strict also fails on warnings)
caliper-docgen check --src srcSettings can also live in a docgen.config.ts (srcDirs, htmlAttributes, cacheDir, and more). caliper-docgen --help lists the rest, including schema for the JSON Schema of the output.
Parsed declarations and the dependency index are cached under node_modules/.cache/caliper-docgen. --no-cache (config, NAPI, Vite and Storybook option cache: false) reads and writes nothing there; the output is the same.
Types your props import from installed packages are resolved by default: the declarations those names point to are loaded. --no-load-dependencies (config, NAPI and Vite option loadDependencies: false) leaves them as named references with an UNRESOLVABLE_IMPORT warning, and a project with no packages installed loads nothing either way. A package source contributing more than 30 props is then listed as one inherited layer with its notable props, as Storybook does; --collapse-inherited-props all does the same for the project's own sources and none lists every prop.
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { caliperDocgen } from 'caliper-docgen-vite-plugin'
export default defineConfig({
plugins: [react(), caliperDocgen({ srcDirs: ['src'] })],
})import docgen from 'virtual:caliper-docgen'
docgen.components['Button'].propsThe module updates through HMR as components change. The virtual module is the canonical format; the injectDocgenInfo option assigns __docgenInfo in the react-docgen-typescript shape (see the plugin README).
// .storybook/main.ts
export default {
framework: '@storybook/react-vite',
addons: [{ name: 'caliper-docgen-storybook', options: { srcDirs: ['src'] } }],
}Storybook 11's docgen server then takes each component's argTypes from caliper-docgen instead of the TypeScript checker, and hands anything caliper can't document to React's own docgen. See packages/storybook.
| Pattern | Example |
|---|---|
| Function and arrow components | function Button(props: ButtonProps) |
React.forwardRef |
forwardRef<HTMLButtonElement, ButtonProps>(...) |
| Wrapped components | styled(Base)<ButtonProps> |
| Interface inheritance | interface ButtonProps extends HTMLAttributes<HTMLButtonElement> |
Intersections, Omit, Pick |
type ButtonProps = Omit<InputProps, 'value'> |
ComponentPropsWithoutRef |
ComponentPropsWithoutRef<'button'> |
VariantProps |
VariantProps<typeof buttonVariants> (CVA, TV, PandaCSS) |
| Discriminated unions | variant: 'filled' | 'outlined' | 'standard' |
| JSDoc tags | @description, @default, @deprecated, @internal |
Components declared through a generic wrapper call, such as Mantine's factory<ButtonFactory>(), are recognized from their type arguments. Not recognized yet: wrapper calls with no type argument, such as lazy(). Props that come from packages that aren't installed stay unresolved and are reported as warnings. docs/edge-cases.md has the measured gaps.
- Project page on orin-dx.com.
- Architecture: pipeline, crate map, invariants.
- Decisions: why OXC, the cache format, panic containment, the planned TypeScript 7 backend.
- Status, edge cases, benchmarks and where Caliper's types differ from RDT and the checker.
- Migrating from
react-docgen-typescript. - Contributing and security.
FSL-1.1-MIT: free for any non-competing use, and each version becomes MIT two years after its release.