Organizing the inspector
A component with a lot of settings doesn't have to show them as one long flat list. section(), tab(), and badge() are presentational tools for structuring the settings inspector - they don't change the data your component stores.
Sections
section() groups fields under a named, collapsible heading - a presentation concern only, not a data shape (that's what group() is for; the two are unrelated despite the similar name):
public static function settings(): Settings
{
return Settings::make()
->section('Content', function (Settings $settings) {
$settings->text('heading')->label('Heading')->required();
$settings->textarea('subheading')->label('Subheading');
})
->section('Appearance', function (Settings $settings) {
$settings->select('alignment')->label('Alignment')->options(['left' => 'Left', 'center' => 'Centered']);
});
}
Collapse state is remembered per group name (localStorage), not per page or component instance.
Tabs
tab() works the same way but produces actual tab navigation in the inspector - only when a component declares more than one. A component with a single tab() call (or none at all) renders flat, with no tab bar:
public static function settings(): Settings
{
return Settings::make()
->tab('Content', function (Settings $settings) {
$settings->text('heading')->label('Heading');
})
->tab('SEO', function (Settings $settings) {
$settings->text('metaTitle')->label('Meta title');
});
}
tab() and section() compose - a section() call inside a tab()'s callback produces a collapsible group within that tab.
Badges
badge() attaches a short, presentational callout next to a field's label (e.g. "New", "Beta"):
$settings->link('cta')->label('Button')->badge('New');
A required field (->required()) that's currently empty shows a calm inline hint in the inspector, not an alarming one - being unfilled while a page is mid-composition is a normal state, not an error.
Conditional visibility
visibleWhen()/hiddenWhen() control whether a field appears in the inspector at all - they're a UI convenience only, never a security boundary. An undeclared or hidden setting can never be persisted regardless:
public static function settings(): Settings
{
return Settings::make()
->toggle('showCta')->label('Show call to action')
->text('ctaLabel')->label('Button label')->visibleWhen('showCta', true);
}
Your own Blade markup makes the same decision independently, using the resolved property value:
@if ($showCta)
<a href="#">{{ $ctaLabel }}</a>
@endif
In the editor itself, a field that depends on another appears or disappears instantly as the field it depends on changes, with a brief animation rather than a jump.