bgaze/bootstrap-form
bgaze/bootstrap-form is a Laravel package that generates Bootstrap-ready forms through a fluent PHP API. It handles model binding, validation errors, old input, labels, help text, and layout classes, so you can build accessible, styled forms without writing repetitive HTML markup.
This release is additive: escape defaults to false, so upgrading from an earlier 4.x is a drop-in with no template changes. Start by confirming you are on the 4.x line, then read docs/llm/options-and-attributes.md (§ Content escaping) and docs/llm/input-groups.md (the addon truth table) — those two pages are now the authoritative reference for everything in this release.
The fastest first use case is a single label that needs to render markup deliberately, or a single label that receives untrusted text:
// Raw by default — unchanged behaviour
BF::text('q', '<b>Bold</b> & co');
// Opt in at the call site
BF::text('q', $userProvidedTitle, null, ['escape' => true]);
The fourth argument is the per-field settings array, so escape slots in alongside your existing field options. If you build forms through a base class or a form object, the same setting is available at the per-form level, and config sets the application default — pick one layer and stay there rather than sprinkling it across all three.
Second first step: audit your addons. Anywhere prepend/append values are interpolated from data (units, currency codes, counts) is a place where the old value-based heuristic could silently flip between escaped and raw depending on the data. Those are the call sites that benefit most from an explicit 'escape' => true.
Cascade deliberately, not accidentally. escape follows config → per form → per field. In practice: leave the config default false unless your application is uniformly rendering user data through sinks, then set it per form where it applies and per field for the exceptions. A global true is viable precisely because Htmlable values are exempt — but it changes the meaning of every existing raw label in the codebase, so treat it as a refactor, not a config tweak.
Markup belongs in Htmlable, not in escaping flags. The idiomatic split the release pushes you toward:
HtmlString, a rendered component) → leave escape off; it is never escaped regardless of the setting.'escape' => true at the sink, and escape at the boundary before it gets there.This is what keeps x-component slots working under a global escape => true:
<x-bf::text name="amount" escape>
<x-slot:append><button type="button" class="btn btn-outline-secondary">Go</button></x-slot:append>
</x-bf::text>
Treat boolean settings as booleans on components. Boolean settings (escape, switch, inline, custom, show-all-errors, show-valid-feedback, disable-errors) now read strictly from "true" / "false". The old escape="false" footgun — the string 'false' being truthy and turning the feature on — is fixed. On x-components prefer the bare attribute for true and ="false" for false; do not write ="0" or ="" and expect a boolean.
Build input groups as slots, not strings. With addons, the pattern that survives an escape => true sweep is: buttons and icons go in slots (Htmlable), textual data goes in prepend/append with escape on. That keeps the visual group intact while making the escaping decision explicit per addon rather than per value.
Wrap repeated sink configuration. If most fields in a form share settings, set them once at the form level and override per field. Duplicating ['escape' => true] on fifty BF::text() calls is a smell that the form-level default was the right lever.
escape is not a security boundary. The release says this plainly and it bears repeating: user input, database content and translation strings must be escaped before they reach a sink, whether or not the setting is on. escape => true is a rendering convenience for call sites you control, not a substitute for {{ }} at the application edge. Do not let it become the reason a raw DB value is passed into a label.
Not covered, by design. required_mark is configuration and its HTML is a documented feature — it will not be escaped by this setting. Validation messages are outside the sink system entirely. BF::label() is standalone and keeps its own per-call argument; escape does not reach it. If your audit assumes "everything in a label is escaped now", those three are the holes.
The addon heuristic is retired only when you opt in. Addons previously chose escaping based on the value — the same call site was escaped for °C and raw the day the value contained a tag. That heuristic is only bypassed when escape is explicitly set; leaving it unset preserves the old, data-dependent behaviour. If you have addons whose output looked inconsistent across environments, this is the explanation, and adding escape => true is the fix.
Double-encoding is now handled, but only for plain-text addons. Addon escaping follows the package-wide no-double-encoding policy already applied to HTML attributes, textarea content and select options. An addon carrying & will render as &, not &amp;. Do not pre-encode to "be safe" — you will now get exactly what you passed, which is the opposite of what the old workaround assumed.
BF::label() accepts HtmlString again. The TypeError on HtmlString arguments is fixed. If you had a defensive (string) cast or a ->toHtml() workaround around a BF::label() call, it can go.
Bootstrap 4 is frozen on purpose. The BS4 baseline was not regenerated and BS4 rendering is unchanged. If you are on Bootstrap 4, this release does not alter your markup at all — escape still works, but nothing about the existing output shifts. Do not expect BS4 snapshots to update.
Debugging an unexpected escape: check the value's type first (Htmlable short-circuits everything), then the per-field array, then the per-form settings, then config. Because the cascade defaults to false, an over-escaped label almost always means someone set it at a broader level than intended. {!! !!} in a Blade slot is the usual culprit for the inverse problem — markup arriving as a plain string and getting escaped when you expected the slot to protect it.
How can I help you explore Laravel packages today?