Component packages
The extension points assume a component lives in your own app. To distribute one — a signature pad, a chart widget, a domain-specific field — you would normally also publish an npm package for its React renderer, and keep it in lockstep with the PHP side. Lattice removes that second release pipeline: a component package ships its React renderer as source inside its Composer package, and the lattice() Vite plugin compiles it straight into your app’s existing build — resolving @lattice-php/lattice against the copy you already installed. A plain composer require is enough.
What a package ships
Section titled “What a package ships”Four things — or let the generator write them. Point --package at a directory that has no composer.json yet and Lattice scaffolds the whole package on first use (the name and PSR-4 namespace are derived from the folder — packages/acme-signature → acme/signature, Acme\Signature\), then creates the component inside it:
php artisan lattice:component Signature --package=packages/acme-signature1. A composer.json declaring the two Lattice entry points:
{ "name": "acme/lattice-signature", "autoload": { "psr-4": { "Acme\\Signature\\": "src/" } }, "extra": { "lattice": { "plugin": "resources/js/plugin.ts", "discover": ["src"] } }}2. The PHP component, carrying its wire type:
use Lattice\Lattice\Attributes\AsComponent;use Lattice\Lattice\Core\Components\Component;
#[AsComponent('signature')]final class Signature extends Component{ public string $label = 'Sign here';}3. The React renderer for that type:
import type { RendererComponent } from "@lattice-php/lattice/core/types";
const Signature: RendererComponent<"signature"> = ({ node }) => ( <div className="rounded-lt-sm border border-lt-border p-4 text-lt-fg"> {String(node.props?.label ?? "Sign here")} </div>);
export default Signature;4. The plugin entry that registers it:
import { createPlugin, lazyComponent } from "@lattice-php/lattice";
export default createPlugin({ name: "acme-signature", components: { signature: lazyComponent(() => import("./signature")), },});How it reaches the app
Section titled “How it reaches the app”The two extra.lattice keys are each read by one side of the stack:
plugin— thelattice()Vite plugin scansvendor/composer/installed.json, and for every package that declares it, grants Vite filesystem access to the package directory and exposes its plugin under the virtual modulevirtual:lattice/plugins. The package’s TSX compiles straight into the consumer’s bundle: no separate build step, one shared React instance, full tree-shaking.discover— Lattice’s PHP discovery merges these roots intolattice.discover, so the package’s#[AsComponent]classes are picked up byphp artisan lattice:typescript(which typesnode.propsfor the package’s components) and by definition discovery for any forms, tables, or pages the package also ships.
Installing one
Section titled “Installing one”For the consumer, it is a single dependency:
composer require acme/lattice-signatureIf you followed the installation guide, the discovered plugins are already registered — the standard bootstrap passes virtual:lattice/plugins to createLatticeApp, so an installed package registers itself with no further wiring:
import latticePlugins from "virtual:lattice/plugins";
createLatticeApp({ plugins: latticePlugins });(Or merge them onto the registry yourself with extendRegistry(registry, ...latticePlugins).) Then use the component like any built-in:
Signature::make('signature')->label('Sign the contract');Styling
Section titled “Styling”See Registry and types for how the type string couples the two sides, and how generated types keep node.props sound.