ComponentRegistry — Programmatic Discovery
Pushery\ is the canonical PHP-side surface for
discovering every component WireKit ships. Use it when your app needs the
component catalog at runtime — generating an autocomplete list in a
search widget, validating a CMS field against shipped components, building
a custom admin tool, or feeding an AI tool the full component schema.
The registry is the single source of truth. Every CLI command (wirekit:list,
wirekit:show, wirekit:export-json, wirekit:export-api-map) is a thin
wrapper around it — the same data shape, just formatted differently.
Quick Reference
use Pushery\WireKit\ComponentRegistry;
// Every component, keyed by name.
$all = ComponentRegistry::all();
// → ['button' => ['category' => 'Display', 'description' => '...'], ...]
// Just the count.
$count = count(ComponentRegistry::all());
// Just the names.
$names = array_keys(ComponentRegistry::all());
// → ['accordion', 'action-bar', 'alert', 'alert-dialog', ...]
// One component by name.
$meta = ComponentRegistry::get('button');
// → ['category' => 'Display', 'description' => 'Action button with variants and loading state']
// Every component in one category.
$formComponents = ComponentRegistry::category('Form');
// → ['checkbox' => [...], 'combobox' => [...], 'date-picker' => [...], ...]
// Every category that has at least one component.
$categories = ComponentRegistry::categories();
// → ['Form', 'Display', 'Navigation', 'Layout', 'Marketing', 'Typography', 'Overlay', 'System']
// Props for a single component (the @props([...]) block, parsed).
$props = ComponentRegistry::extractProps('stat');
// → [
// ['name' => 'label', 'default' => 'null', 'default_normalized' => 'null', 'type_hint' => null, 'comment' => null],
// ['name' => 'value', 'default' => 'null', ...],
// ...
// ]
Methods
ComponentRegistry::all()
Returns every component keyed by its slug name. Stable shape across releases — adding a new component appends an entry; renaming requires a major-version bump.
/** @return array<string, array{category: string, description: string}> */
public static function all(): array;
ComponentRegistry::get(string $name)
Returns the metadata for one component, or null when the name isn't
registered. Same shape per-entry as all().
/** @return array{category: string, description: string}|null */
public static function get(string $name): ?array;
ComponentRegistry::category(string $category)
Returns every component in a category. Empty array when the category doesn't exist.
/** @return array<string, array{category: string, description: string}> */
public static function category(string $category): array;
ComponentRegistry::categories()
Returns every unique category name across the registry. Order is insertion-order, which matches the source-file declaration sequence.
/** @return list<string> */
public static function categories(): array;
ComponentRegistry::extractProps(string $name)
Returns the structured @props([...]) block for a component. Routes
through Pushery\, which uses PHP's own
tokenizer — no regex parsing of the prop expressions, so config(...)
defaults, multi-line array literals, match(...) expressions, and
heredoc / nowdoc values all parse cleanly.
/**
* @return list<array{
* name: string,
* default: ?string,
* default_normalized: ?string,
* type_hint: ?string,
* comment: ?string,
* examples: list<string>,
* values: ?list<string>,
* value_type: ?string,
* }>
*/
public static function extractProps(string $name): array;
Per-record fields:
| Field | Purpose |
|---|---|
name |
Prop name (string-key from the @props array, quote-stripped). |
default |
Raw default expression as it appears in source. null when the prop has no => default clause. |
default_normalized |
Same expression with whitespace collapsed and comments stripped. Stable for string comparison. |
type_hint |
The declared PHP type, for a class-based component whose props are read from its constructor signature. null for an anonymous Blade component, because a @props block declares no types. |
examples |
Example values the prop's documentation names, as a list. Empty for most props. |
comment |
The trailing same-line // … comment after the prop's comma, if any. Leading // stripped + trimmed. |
values |
The values validateProp() accepts for the prop, as a list. null when the prop is checked against no fixed set — including a prop whose rule is a pattern, where the call carries a sentence for the error text rather than an enumeration. |
value_type |
What the template proves the value is: bool, int, float, string or array, read from a declared type, from the default's own literal (a config(…) default through to its fallback), or from the allow-list. null when nothing in the template says. |
ComponentRegistry::extractAwareProps(string $name)
Returns the names a component accepts through @aware rather than @props.
Deliberately separate from extractProps(): an @aware key is a value the
PARENT owns, which the child reads if it is there, so folding the two together
would widen every surface that means "the props this component declares". Same
per-record shape as extractProps(); always empty for a class-based component.
/** @return list<array{name: string, default: ?string, comment: ?string, ...}> */
public static function extractAwareProps(string $name): array;
ComponentRegistry::acceptedPropNames(string $name)
Returns every name the component's tag accepts — its @props and its @aware
keys together, as a flat list. Use this when the question is "would Blade do
anything with this attribute?", which is what a linter, an editor completion or
an unknown-prop warning is really asking.
It is deliberately a different answer from extractProps(). @aware resolves
against the data of the component being rendered before it walks up to the
parent, so a key written straight onto the child tag arrives and takes effect —
<x-wirekit:: renders the separated chrome
even though variant is not in that component's @props. A tool that reads the
declared props alone reports such a call as a typo.
/** @return list<string> */
public static function acceptedPropNames(string $name): array;
ComponentRegistry::resolve(string $name)
Returns metadata for either shape of name — card and card.body both answer.
get() knows only top-level components and returns null for a sub-component,
which reads as "no such component" to a caller that does not know the
difference.
/** @return array{category: string, description: string, parent?: string}|null */
public static function resolve(string $name): ?array;
ComponentRegistry::tag(string $name) and ComponentRegistry::tagAlias(string $name)
tag() returns the tag to write for a component. Do not interpolate
<x-wirekit::{name}> yourself: a class-based component's canonical tag uses the
single-hyphen form. tagAlias() returns the historical double-colon spelling
where one exists, and null otherwise — which is every anonymous component.
public static function tag(string $name): string;
public static function tagAlias(string $name): ?string;
ComponentRegistry::subComponents() , ComponentRegistry::subComponentsOf(string $parent) and ComponentRegistry::isSubComponent(string $name)
The sub-component surface. subComponents() returns every one in the catalog,
sub the ones belonging to a parent (card → card.body,
card.footer, card.header), and is answers for a single name.
/** @return list<string> */
public static function subComponents(): array;
/** @return list<string> */
public static function subComponentsOf(string $parent): array;
public static function isSubComponent(string $name): bool;
ComponentRegistry::describeSubComponentsOf(string $parent)
The same set, each with the props it declares and the tokens it reads. A bare name
tells a tool that table.th exists and nothing else, so its documented
headerScope prop was unreachable through every surface fed by the name list alone.
/** @return list<array{name: string, tag: string, props: list<array<string, mixed>>, tokens: list<string>}> */
public static function describeSubComponentsOf(string $parent): array;
ComponentRegistry::slotsOf(string $name)
Returns the slots a component's template declares — the default slot and any
named ones — each with whether it is required. The class-side exclusion is part
of the answer rather than the caller's job: a class-based component's public
properties appear in its template as {{ $name }}, which a slot scanner reads
as a required named slot unless told otherwise.
/** @return list<array{name: string, required: bool}> */
public static function slotsOf(string $name): array;
ComponentRegistry::tokensOf(string $name)
Returns the design tokens a component reads, sorted: the custom properties declared
in dist/ that its template, the partials it includes, the package
helpers it calls and the stylesheet rules on its own wk- classes and data-wk-*
attributes read. A sub-component answers for itself and a parent never for its
parts, so each token is listed beside the element that reads it. The list is
derived on every call, which is why it cannot fall behind the markup. A value a
script reads at runtime is the one kind it does not see.
/** @return list<string> */
public static function tokensOf(string $name): array;
ComponentRegistry::componentClass(string $name)
Returns the backing class for a class-based component, or null for an
anonymous Blade one. The cheapest way to ask which of the two shapes you are
holding.
/** @return class-string|null */
public static function componentClass(string $name): ?string;
ComponentRegistry::existingBladeFilePath(string $name)
Returns the component's Blade template, or null when it has none. Resolves the
flat, dotted and directory-index filename forms, which is why it exists: a second
resolver that knew only the first two reported the one component written in the
third as having no template, and "no template" silently became "no slots".
public static function existingBladeFilePath(string $name): ?string;
AI Tooling / LLM Consumption
This page is the canonical programmatic API for component discovery.
AI tools (Cursor, Claude Code, MCP servers, Aider) should prefer the PHP
surface above OR the matching CLI flags below — both are stable across
releases. Do not parse wirekit:show output programmatically — it's
optimized for human reading and isn't shape-stable.
Machine-readable CLI surface
| Need | Use |
|---|---|
| Total component count | php artisan wirekit:list --as=count |
| Component name list | php artisan wirekit:list --as=slugs |
| Per-category count map | php artisan wirekit:list --as=categories |
| Full per-component metadata | php artisan wirekit:list --as=json |
| Single component schema (props + sub-components) | php artisan wirekit:show <name> --as=json |
| Full manifest with slots | php artisan wirekit:export-json --pretty |
All output is plain-stdout JSON with no decoration, suitable for jq
pipes. The schemas are stable for v2.x — additive new fields may appear,
existing field shapes won't change without a major-version bump.
Categories
The canonical category enum across Component:
| Category | Definition | Examples |
|---|---|---|
Form |
Inputs and form-related controls. Carries focus/validation semantics. | input, select, combobox, date-picker, file-upload, checkbox, toggle |
Layout |
Structural primitives that compose page chrome and content regions. No conversion intent. | app-shell, container, grid, stack, header, main, sidebar, footer |
Typography |
Text-rendering primitives — body copy, headings, inline marks. | heading, text, prose, link, code, code-block, blockquote, kbd |
Navigation |
Wayfinding chrome and link surfaces. | navbar, tabs, breadcrumb, sidebar, pagination, stepper, brand-bar |
Overlay |
Layered surfaces that float above content (modal, drawer, popover, tooltip). | modal, drawer, dropdown, popover, tooltip, command-palette, alert-dialog |
Display |
Generic content presentation — cards, alerts, badges, charts. | card, alert, badge, button, stat, chart, calendar, avatar |
Marketing |
Components whose PRIMARY purpose is marketing / conversion. Narrow scope, clear semantic — added in v2.1.0. | cta, feature, feature-grid, hero |
System |
Infrastructure / glue primitives — icon resolution, font registration, structured-data emitters. | chart, icon, fonts, glass, structured-data |
Usage criteria. When deciding which category a new component belongs to, ask: "What is the component's primary use-case?"
- A
ctaprimitive lives or dies by its conversion-funnel role →Marketing. - A
revealanimation primitive serves marketing landing pages but is also generic →Display. - A
footeris used on every page including non-marketing →Layout. - A
brand-baris header chrome reused across marketing, app, and docs sites →Navigation.
The Marketing category was added in v2.1.0 with four canonical entries. Reserve it for components whose conversion intent is their defining trait — not every component USED on a marketing page belongs here.
Categories are stable for v2.x. Re-categorization is a ### Changed
entry in the public CHANGELOG.
See Also
- Authoring Custom Components — how to
write your own
@props([...])blocks and have them parse cleanly. - Authoring Custom Alpine Plugins — defensive-cleanup pattern for plugins that drive your components.
- CLI Reference — the full command surface.