Frontend Widgets
Frontend widgets are registered public components that Capell can render in normal content, lazy interaction targets, and package-owned experiences. A widget definition answers four questions:
| Question | Defined by |
|---|---|
| Which widget key can editors choose? | WidgetDefinitionData::$key |
| Which Blade/Livewire component renders it? | WidgetDefinitionData::$component and target |
| Which frontend assets does it need? | resourceGroups |
| How should it present, load, and expose interactions by default? | defaultPresentationSettings and defaultInteractionTriggers |
Content Sections and Layout Builder use this same surface for editor-managed content. The registry lives in the core/frontend boundary so packages can ship reusable widget targets without inventing their own modal systems, asset loaders, or public routes.
Register A Widget
Section titled “Register A Widget”Use WidgetRegistry::registerDefinition() when the widget needs presentation defaults, runtime resource groups, or interaction defaults.
use Capell\Core\Data\Widgets\WidgetDefinitionData;use Capell\Core\Support\Widgets\WidgetRegistry;
public function boot(WidgetRegistry $widgets): void{ $widgets->registerDefinition(WidgetDefinitionData::frontendBlade( key: 'video-player', component: 'vendor-video::widgets.player', resourceGroups: ['vendor-video.player'], defaultPresentationSettings: [ 'width_mode' => 'container', 'loading_strategy' => 'interaction', ], ));}WidgetRegistry::register($name, WidgetTarget::FrontendBlade, $component) still works for simple widgets. New package code should prefer definitions because the rendering component, resource groups, presentation defaults, and interaction defaults stay in one place.
Instance State
Section titled “Instance State”Widget instance data has two layers:
| Layer | Purpose | Public component receives it? |
|---|---|---|
data.* | The widget’s own content props | Yes |
data.__capell.* | Capell runtime, presentation, and interaction metadata | No |
For example:
[ 'type' => 'video-player', 'data' => [ 'title' => 'Product walkthrough', 'video_url' => 'https://example.com/video.mp4', '__capell' => [ 'presentation' => [ 'loading_strategy' => 'visible', ], 'interactions' => [ [ 'label' => 'Open transcript', 'target_type' => 'widget', 'behavior' => 'modal', 'target_widget' => [ ['type' => 'content', 'data' => ['content' => '<p>Transcript...</p>']], ], ], ], ], ],]The public renderer strips data.__capell before passing props to the widget component. A video widget can receive title and video_url; it must not receive its presentation settings, nested target widgets, editor metadata, or interaction internals.
Presentation Defaults And Overrides
Section titled “Presentation Defaults And Overrides”Widget definitions can provide type defaults through defaultPresentationSettings. Editors can override those settings on one widget instance under data.__capell.presentation.
Resolution order is:
- instance override in
data.__capell.presentation; - widget definition default;
- presentation preset default;
- system default.
The system default keeps existing content server-rendered. Only opt a widget or block into lazy behaviour when it should genuinely wait for visibility, idle time, or visitor interaction.
Interaction Targets
Section titled “Interaction Targets”Widgets can expose interaction triggers through data.__capell.interactions or type defaults in WidgetDefinitionData::$defaultInteractionTriggers.
Supported target types:
| Target | Use |
|---|---|
widget | Render a registered widget through the lazy widget endpoint. |
fragment | Render an encrypted Layout Builder block fragment. |
url | Link to a safe URL. |
public_action | Use a safe fallback URL unless a package renders the action elsewhere. |
Supported behaviours for lazy targets are modal, slide_over, inline_reveal, and replace_region.
Widget targets render through /_capell/widgets/{reference}. The reference is encrypted JSON containing the widget type and data. The public trigger does not expose the widget type, component name, package name, target content, model IDs, field paths, or editor metadata.
Use a widget target when the visitor is opening a separate experience, such as a video player, form, gallery, quote calculator, or comparison panel. Use a Layout Builder fragment target when the visitor is loading a public block fragment from the current layout.
Example: Button Opens A Video Widget
Section titled “Example: Button Opens A Video Widget”The editor-facing shape for a trigger that opens a video in a modal looks like this:
[ 'label' => 'Watch tour', 'icon' => 'heroicon-o-play-circle', 'style' => 'primary', 'target_type' => 'widget', 'behavior' => 'modal', 'modal_size' => 'lg', 'target_widget' => [ [ 'type' => 'video-player', 'data' => [ 'title' => 'Product tour', 'video_url' => 'https://example.com/product-tour.mp4', ], ], ],]The public page renders a safe trigger and an encrypted lazy widget URL. It does not render the target widget content until the visitor clicks.
Runtime Resources
Section titled “Runtime Resources”Use FrontendResourceRegistry when a widget needs CSS or JavaScript that should only load when the widget appears or when an interaction target opens.
use Capell\Core\Enums\PresentationLoadingStrategy;use Capell\Frontend\Support\Assets\FrontendResourceRegistry;
public function boot(FrontendResourceRegistry $resources): void{ $resources ->group('vendor-video.player') ->css('resources/css/player.css', buildPath: 'vendor/vendor-video') ->js('resources/js/player.js', buildPath: 'vendor/vendor-video', loading: PresentationLoadingStrategy::Interaction);}Public HTML receives generated resource IDs. Resource group keys and package names stay out of the rendered page.
Choosing Loading Behaviour
Section titled “Choosing Loading Behaviour”| Loading strategy | Use when |
|---|---|
eager | The widget is visible and needed for the first render. |
visible | The widget can wait until it enters the viewport. |
interaction | The widget is only needed after a click, focus, or similar action. |
idle | The widget is useful soon, but not critical to initial rendering. |
For interaction targets, prefer interaction resources unless the target also appears server-rendered elsewhere on the page.
Public Output Rules
Section titled “Public Output Rules”Widget HTML and interaction placeholders must not expose:
- admin/editor controls;
- model IDs;
- field paths;
- block keys;
- component names;
- package namespaces;
- signed URLs;
- raw target widget data.
Use Public HTML safety, Presentation delivery, and Frontend extensions when changing widget rendering.