ComponentRegistry — Programmatic Discovery
Pushery\WireKit\ComponentRegistry 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', 'Layout', 'Typography', 'Navigation', 'Overlay', 'Display', '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\WireKit\Support\PropsParser, 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,
* }>
*/
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. |
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::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,
subComponentsOf() the ones belonging to a parent (card → card.body,
card.footer, card.header), and isSubComponent() 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 actually declares. 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>>}> */
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::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 ComponentRegistry:
| 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.