moonshine / ui
MoonShine UI
Requires
- php: ^8.4
- ext-json: *
- illuminate/contracts: ^11|^12|^13
- illuminate/support: ^11|^12|^13
- illuminate/view: ^11|^12|^13
- moonshine/asset-manager: ^5.0
- moonshine/contracts: ^5.0
- moonshine/core: ^5.0
- moonshine/support: ^5.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- 5.x-dev
- 5.0.0
- 4.x-dev
- 4.21.0
- 4.20.1
- 4.19.6
- 4.19.5
- 4.19.3
- 4.19.1
- 4.19.0
- 4.18.2
- 4.16.1
- 4.15.0
- 4.13.0
- 4.12.0
- 4.11.0
- 4.10.0
- 4.9.1
- 4.8.0
- 4.7.0
- 4.5.1
- 4.5.0
- 4.4.6
- 4.4.5
- 4.4.3
- 4.4.2
- 4.4.1
- 4.4.0
- 4.3.5
- 4.3.4
- 4.3.3
- 4.3.1
- 4.3.0
- 4.2.2
- 4.2.1
- 4.2.0
- 4.1.6
- 4.1.5
- 4.1.4
- 4.1.3
- 4.1.1
- 4.1.0
- 4.0.9
- 4.0.8
- 4.0.7
- 4.0.5
- 4.0.4
- 4.0.2
- 4.0.1
- 4.0.0
- 3.x-dev
- 3.18.0
- 3.16.4
- 3.16.2
- 3.15.8
- 3.15.5
- 3.15.3
- 3.15.2
- 3.15.1
- 3.15.0
- 3.14.4
- 3.14.3
- 3.14.2
- 3.14.1
- 3.14.0
- 3.13.6
- 3.13.5
- 3.13.3
- 3.13.2
- 3.13.1
- 3.13.0
- 3.12.6
- 3.12.4
- 3.12.3
- 3.12.2
- 3.12.0
- 3.11.0
- 3.10.3
- 3.10.2
- 3.10.1
- 3.10.0
- 3.9.1
- 3.8.11
- 3.8.8
- 3.8.6
- 3.8.4
- 3.8.2
- 3.8.1
- 3.8.0
- 3.7.6
- 3.7.4
- 3.7.1
- 3.7.0
- 3.6.3
- 3.6.0
- 3.5.2
- 3.5.0-rc.2
- 3.5.0-rc
- 3.4.8
- 3.4.7
- 3.4.6
- 3.4.5
- 3.4.3
- 3.4.1
- 3.4.0
- 3.3.6
- 3.3.5
- 3.3.3
- 3.3.2
- 3.3.1
- 3.2.3
- 3.2.1
- 3.2.0
- 3.1.4
- 3.1.2
- 3.1.1
- 3.1.0
- 3.0.5
- 3.0.4
- 3.0.3
- 3.0.2
- 3.0.1
- 3.0.0
- 3.0.0-rc.1
- 3.0.0-beta.2
- 3.0.0-beta.1
- 3.0.0-alpha
This package is auto-updated.
Last update: 2026-11-12 14:58:45 UTC
README
This repository is a readonly MoonShine monorepo subsplit. Please, open pull requests and issues in the main repository.
Display escaping
Labels, hints, prefixes, suffixes and string content returned by beforeRender() / afterRender() are escaped by default. This also applies to field labels rendered in tables, cards and relation headings, and to component labels such as buttons, links and tabs.
Allow HTML for an individual part explicitly:
Text::make('<strong>Name</strong>', 'name') ->unescapeLabel() ->hint('<a href="/help">Help</a>') ->unescapeHint() ->prefix('<span>Prefix</span>') ->unescapePrefix() ->suffix('<span>Suffix</span>') ->unescapeSuffix() ->beforeRender(fn () => '<aside>Before</aside>') ->unescapeBeforeRender() ->afterRender(fn () => '<aside>After</aside>') ->unescapeAfterRender();
Each unescape…() method has an escape…(bool $escape = true) counterpart. An explicit field/component setting takes precedence over the global setting in either direction. Label methods are also available on components with labels, for example ActionButton::make('<b>Open</b>')->unescapeLabel(). Footer applies its label preference to menu labels, and Tabs applies it to tabs created from items.
Text derived from field values has its own preference, so allowing HTML in a field label never exposes record data:
Text::make('<b>Site</b>', 'site') ->unescapeLabel() // field label only ->link(fn ($value) => $value, name: fn ($value) => $value) ->unescapeLinkName(); // link names, opt in separately BelongsToMany::make('Categories', 'categories', resource: CategoryResource::class) ->tree('parent_id') ->unescapeOptionLabels(); // checkbox table, tree, horizontal and inline labels
Heading labels, explicit Modal / OffCanvas titles, field link names and related item labels remain escaped even when global label escaping is disabled, because they commonly contain record data. Use their local unescapeLabel(), unescapeTitle(), unescapeLinkName() or unescapeOptionLabels() only for trusted HTML. A title derived from an action button label follows the button's label preference; relation headings and modal titles follow the relation field's label preference. Confirmation content and explicit confirmation titles keep escaping enabled by default.
Htmlable values, such as HtmlString, are treated as prepared HTML. Return one from beforeRender() / afterRender() to output trusted markup without disabling escaping for the field. getBeforeRender() / getAfterRender() keep their Renderable|string return type: such values, including generated xIf() wrappers, are returned as RenderableHtml.
In Laravel, configure the defaults in config/moonshine.php:
'escapes' => [ 'label' => true, 'hint' => true, 'prefix' => true, 'suffix' => true, 'before_render' => true, 'after_render' => true, ],
The corresponding MoonShineConfigurator methods are escapeLabel(), escapeHint(), escapePrefix(), escapeSuffix(), escapeBeforeRender() and escapeAfterRender(). Pass false to disable a default. Values from env() such as '0', 'false' or 'off' are accepted; empty or unrecognized values keep escaping enabled.
The global label setting applies to developer-provided labels without a local preference, including buttons, fields, boxes, dividers, menus and card value labels. Disabling it allows HTML in those labels; escape any record data included in them explicitly. The record-text contexts listed above keep their own escaping defaults. Prefer local unescape…() methods when only specific labels contain HTML.
These settings control display text. getLabel() and getHint() still return the original text, and existing value methods escape(), unescape() and escapeOnApply() keep their separate purpose. Renderable views and generated component markup are rendered as HTML. When upgrading HTML supplied as a plain string, enable the appropriate unescape…() method explicitly.
Relation modalMode() buttons inherit the field's label preference; modifyButton can override it. link() name callbacks receive the field preview as before, for example the option label of a Select or the formatted Date. While link names are escaped, fields that escape their own preview (Text, Textarea, BelongsTo) pass it as plain text, and the callback result is escaped once; other fields pass their prepared preview. When that preview is markup generated by the field (Checkbox, Color, Json, File, Number stars, multiple Select badges and Enum lists), the parts of the result that repeat it unchanged keep their prepared form; custom fields declare this by overriding isPreviewMarkup(); Url::title() callbacks likewise receive the raw value. After unescapeLinkName() callbacks receive the prepared preview and their result is output as trusted HTML. Inline BelongsToMany badge callbacks receive the prepared label, because badges print their value as is. A Link returned by its inline link callback follows the option label preference unless it sets its own label preference. Async search results selected into the checkbox table are inserted as text unless the field calls unescapeOptionLabels(). Popover editing uses the field's value escape() / unescape() preference independently of global or local label settings.
To reuse a label in another component, use labelFrom(): it copies both the label and its escaping preference, for example Heading::make()->labelFrom($field).
Table column-selection toggles inherit each field's label preference. An explicit handler label preference applies only when its button has no explicit label preference of its own. modifyButton runs in prepareButton() afterward and has the last word: the button it returns is used as is. Handlers::getButtons() applies the handler preference only to buttons that a custom getButton() returns without calling prepareButton().
Prefix/suffix rendering uses the field's isEscapePrefix() / isEscapeSuffix() getters, including overrides in custom fields. The default getters use the explicit field preference, then an explicit extension preference, then the global default. Rendering prepares a clone of the extension, so shared extension instances keep their own preferences. Standalone extensions use the same defaults through DisplayTextType.
Display escaping is prepared in PHP. getLabelHtml() and getHintHtml() return prepared Htmlable values, and class-backed components pass them to their views as labelHtml, hintHtml, valueHtml or titleHtml next to the raw text. Blade outputs prepared values with {{ }} without escaping them again; they serialize to strings. Templates do not read escaping configuration or call PHP helpers: all preparation happens in the components' view data. Raw labels and value keys remain available for serialization and attributes.
Anonymous Blade components used directly follow the standard Laravel idiom: plain strings are escaped, while Htmlable values such as HtmlString and Blade slots are output as trusted HTML. The global escapes.* settings apply to values prepared by class-backed components, not to raw strings passed directly to anonymous views.
Class-backed Blade components accept a local preference through :escape-label, for example <x-moonshine::action-button :label="$label" :escape-label="false" />. The constructor receives this preference before view data is prepared. A direct Blade Link displays its label when its slot is empty; an explicit slot takes precedence. Link passes its prepared label as $slot, so published link-native / link-button views keep working. Card labels also respect values, escapeLabel, and escapeValueLabels supplied through customView(). A label or title supplied through customView() data gets its labelHtml / titleHtml rebuilt with the component's preference, unless the prepared value is supplied too.
The Laravel view composer prepares the slot of link-native / link-button before Blade renders it. Direct view rendering accepts plain strings, Htmlable values and Blade slots; an absent or empty slot falls back to the prepared label. Collapse titles and Footer menu labels replaced through customView() also follow the component's escaping preference, while explicit labelHtml / menuLabelsHtml values take precedence. Card values and Footer menus supplied as iterators through customView() remain available after serialization and repeated rendering. Custom fields can override getExtensionsAttributes() to add extension attributes during rendering.
Custom implementations
Existing contracts are unchanged, so custom implementations keep working after the upgrade. Escaping is described by separate contracts:
HasLabelEscapingContract:escapeLabel(),unescapeLabel(),isEscapeLabel(),hasLabelEscapingOverride()andgetLabelHtml(): Htmlable, supplied by theWithLabeltrait,HasHintEscapingContract:escapeHint(),unescapeHint(),isEscapeHint()andgetHintHtml(): Htmlable, supplied byWithHint,HasLinkNameEscapingContract:escapeLinkName(),unescapeLinkName()andisEscapeLinkName(), supplied byWithLink,HasTitleEscapingContract:escapeTitle(),unescapeTitle()andisEscapeTitle()for modals and off-canvas panels,HasRenderEscapingContract: thebeforeRender()/afterRender()escaping methods of fields,DisplayEscapingConfiguratorContract: the global defaults of a configurator,HasEscapedValueContract: the valueescape(),unescape()andisUnescape()methods supplied byWithEscapedValue, used by popover editing.
Implementations without these contracts use the defaults described above: escaping stays enabled, and configurable types follow the configurator when it implements DisplayEscapingConfiguratorContract. hasLabelEscapingOverride() reports whether a local preference was set, independently of its resolved value, so defaults can be inherited without overwriting explicit choices. Keep getLabel(): string raw for attributes, serialization and data operations, and use getLabelHtml() for display.