Custom Props
A custom Prop is the right tool when you need a new widget type that the built-in Props (TextProp, SelectProp, SliderProp, ColorProp, ToggleProp, NumberProp, SizeProp, MediaProp, IconProp, FontPickerProp, EditorProp, ...) can't express. Typical triggers:
- A date-range picker
- A multi-stop gradient builder
- A typed enum with icons in the dropdown
- A specialized numeric range with both
minandmaxcaptured at once
If the built-in Props cover your case, prefer composing them in a Custom PropType instead. Only build a custom Prop when a NEW widget is genuinely needed.
Scaffold
php artisan make:tagixo-prop RangeProp
Default destination: app/Tagixo/Props/RangeProp.php. Relocate via config('tagixo.scaffolding') or per-command flags.
Skeleton
The class extends Ccast\Tagixo\Core\Props\AbstractProp. The base class ships fluent helpers via make($key), setLabel(), default(), helpText(), variablePicker($type), showWhen(...).
<?php
namespace App\Tagixo\Props;
use Ccast\Tagixo\Core\Props\AbstractProp;
class RangeProp extends AbstractProp
{
public function widget(): string
{
return 'range';
}
public function widgetConfig(): array
{
return $this->baseConfig();
}
}
Required overrides
widget(): string
The widget identifier the frontend uses to dispatch the right Vue component. For schema-driven PropTypes, DynamicPropType.vue reads this value and renders the matching Vue widget. The string must match a widget registered in your frontend bundle.
Built-in widget identifiers (95% of cases): slider, select, toggle, text, textarea, number, color, size, icon, media, font-picker, editor, repeater, tabs, four-ways, box-shadow, model-select, css-declarations.
widgetConfig(): array
Payload sent to the Vue widget. Call $this->baseConfig() to inherit the standard keys (key, label, default, helpText, variablePicker, showWhen, …) and merge your widget-specific config on top.
Optional overrides
toCss(mixed $value): string
Return a CSS fragment if this Prop generates CSS on its own. Most Props don't — the parent PropType usually consolidates output (see FilterPropType or BoxShadowPropType). When in doubt, return ''.
Custom fluent setters
Add chainable methods that configure your widget. Match the pattern of SliderProp::min(), SliderProp::unit(): store on protected props and emit through widgetConfig().
Concrete example
A RangeProp that captures {min, max} values for a numeric range, configurable via min(0)->max(100)->step(1):
<?php
namespace App\Tagixo\Props;
use Ccast\Tagixo\Core\Props\AbstractProp;
class RangeProp extends AbstractProp
{
protected float $min = 0;
protected float $max = 100;
protected float $step = 1;
protected ?string $unit = null;
public function widget(): string
{
return 'range';
}
public function min(float $min): static
{
$this->min = $min;
return $this;
}
public function max(float $max): static
{
$this->max = $max;
return $this;
}
public function step(float $step): static
{
$this->step = $step;
return $this;
}
public function unit(string $unit): static
{
$this->unit = $unit;
return $this;
}
public function widgetConfig(): array
{
return array_merge($this->baseConfig(), [
'min' => $this->min,
'max' => $this->max,
'step' => $this->step,
'unit' => $this->unit,
]);
}
}
Use it inside any PropType schema():
public function schema(): array
{
return [
RangeProp::make('opacity_range')
->setLabel(__('Opacity range'))
->min(0)->max(1)->step(0.05)
->default(['min' => 0, 'max' => 1]),
];
}
Frontend pairing
The corresponding Vue widget MUST be available in the consumer's frontend bundle. Two options:
- Use an existing widget: in 95% of cases one of the built-in widget identifiers fits. Just return its string from
widget()and you're done. - Ship a custom Vue widget: build it in your plugin's frontend bundle, register it in the widget dispatcher, ensure the consumer imports your plugin's assets (see Tagixo Plugin for the asset publication flow).
If your Prop reuses the slider widget but with different defaults, you don't need any Vue code — return 'slider' from widget() and configure via widgetConfig().
Registration
Props are NOT separately registered. They're instantiated inside PropType::schema() and travel with the PropType through the bootstrap payload. The make:tagixo-prop command prints a reminder of this on success.
If your Prop is meant to be shared across many PropTypes in a plugin, the plugin's service provider just needs to register the PROPTYPE — the Prop comes along for the ride.
Reusability
The fluent API makes a custom Prop ergonomic across many PropTypes:
RangeProp::make('opacity')->min(0)->max(1)->step(0.01)
RangeProp::make('zoom')->min(0.5)->max(3)->step(0.1)
RangeProp::make('temperature')->min(-20)->max(40)->unit('°C')
Same widget, different configuration each time.
Testing checklist
- Widget dispatches correctly (
DynamicPropType.vuerenders the right Vue component) widgetConfig()payload contains all custom keys- Defaults persist round-trip (save → reload → render produces identical state)
- Fluent setters return
$thisso chains work variablePicker()andshowWhen()from the base class still work when set