Generating a component API reference you can trust

A props table written by hand is wrong within a month. Here is what it takes to derive one from the code — and the four things that still go wrong afterwards.

A hand-written props table is wrong within a month, and the worst part is that nobody finds out. A wrong sentence in prose gets a bug report. A missing prop in a table gets nothing: the reader assumes the prop does not exist, works around it, and never mentions the workaround to anyone.

So the table has to be derived. That much is obvious and it is where most teams stop, having solved a quarter of the problem. This is what the other three quarters looked like on a catalogue of 108 components across 8 packages.

Deriving is the easy part

vue-component-meta reads the component and returns props, slots, events and methods with their types, defaults and JSDoc. It is a solved problem, and the first version of a generated table takes an afternoon.

The interesting decision is where the generator runs. Running it on the documentation site seemed natural and is wrong: it needs the library’s whole toolchain, takes minutes, and — the part that matters — produces a result that can differ from the one the library itself published. The rule that came out of it is worth stealing:

Whatever is generated is generated once, by whoever owns it. Everyone else reads the artefact.

The library emits componentApi.generated.json in its release. The portal reads it and never recomputes it. A consumer that regenerates someone else’s data has quietly created a second source of truth.

Failure one: the table is honest and useless

The first generated tables were correct and unreadable, because they showed everything the tool returned. Empty sections — “Events: 0”, “Methods: 0” — appeared on every component that had none.

An empty section is not neutral. It announces absence in the place where the reader is looking for presence, and it does it 108 times. Sections with no items are now dropped entirely, and the page is shorter and truer for it.

Failure two: the description is in the wrong language

This one is specific to this project and general in shape. The JSDoc comments in the library are written in Russian — a repository rule, and a reasonable one for internal explanation. But vue-component-meta does not distinguish “internal comment” from “public documentation”. It just extracts.

The result: the English component page showed a Russian props table. 862 descriptions out of 865 — more than half of everything written on the page.

There is a correct fix and a possible one. The correct fix is English JSDoc in the library, because that also fixes IDE tooltips, web-types and the MCP server, all of which read the same comments. It changes a repository rule, so it is not the documentation site’s call to make.

The possible fix is what shipped: a translation table keyed by component.section.name, with the Russian original stored next to the English text. The original is not sentimentality — it is the fingerprint. When the description changes in the library, the stored original stops matching, and the build fails with the exact key. Without it, a stale translation is indistinguishable from a fresh one.

Failure three: the gate that checks itself

The obvious gate compares the translation table against the library. The tempting shortcut is to build the expected set from the same module the page uses — and that gate proves nothing at all, because both sides come from one place.

The rule that survived: the gate reads the source independently. It walks the component API artefact itself and rebuilds the expected set of keys, then requires four things of the table — the row exists, the original has not moved, the translation is not empty, and there is no Cyrillic left in the English column. Plus the reverse direction: a row whose description no longer exists in the library is an error too, because a table that outlives its subject looks maintained.

A second gate reads the built pages rather than the sources, and looks for foreign-language text in the shell. It is deliberately asymmetric: on a Russian page it looks for a run of three English words, because isolated English words are legitimate everywhere in technical writing; on an English page it looks for any Cyrillic at all, because Cyrillic in English prose never is.

Failure four: the playground and the table disagreed

The API table and the interactive playground were built from the same data, so they could not disagree — until the translation landed and only the table was localised. For one build, a component page showed English descriptions in the table and Russian ones in the playground controls.

The fix is structural rather than careful: the page resolves the API once, in the page’s language, and everything downstream — the table, the playground, the Markdown version served to agents — consumes that. Two languages in two places on one page do not read as a translation in progress. They read as a broken page.

What it costs to keep

Four gates and one table. What it buys is narrower than “correct documentation” and worth stating precisely: the props table cannot silently disagree with the package the reader is installing. It can be incomplete, it can be badly worded, and the prose around it can be out of date. It cannot be wrong about the API.

That is a smaller promise than most documentation makes, and unlike most, it is one you can check on every commit.

Components in this article