portavice / bladestrap
Blade components for Bootstrap 5
Requires
- php: ^8.1
- illuminate/console: ^10.24|^11.0
- illuminate/http: ^10.24|^11.0
- illuminate/support: ^10.24|^11.0
- illuminate/view: ^10.24|^11.0
Requires (Dev)
- orchestra/testbench: ^8.22|^9.0
- phpunit/phpunit: ^10.4|^11.1
- portavice/laravel-pint-config: ^2.0
README
Bladestrap provides Laravel Blade components for the Bootstrap 5 frontend framework.
Contents
Installation
First, install the package via Composer:
composer require portavice/bladestrap
Within a Laravel application, the package will automatically register itself.
Note
If you only use parts of the Laravel framework (such as illuminate/view
),
make sure to follow the instructions in the section on usage without Laravel.
Install Bootstrap
Note that you need to include the Bootstrap files on your own.
- If you haven't added Bootstrap as one of your dependencies, you can do so via npm:
npm install bootstrap
- Add the following to your
webpack.mix.js
to copy the required Bootstrap files to yourpublic
directory:let bootstrapFiles = [ 'node_modules/bootstrap/dist/css/bootstrap.min.css', 'node_modules/bootstrap/dist/js/bootstrap.bundle.min.js', ]; mix.copy(bootstrapFiles, 'public/lib');
- Include CSS and JavaScript in
resources/views/layouts/app.blade.php
:<link rel="stylesheet" href="{{ mix('lib/bootstrap.min.css') }}"> <script src="{{ mix('lib/bootstrap.bundle.min.js') }}"></script>
You may need to adjust the steps above to your custom project configuration. If you have a custom Bootstrap build you are responsible to include the necessary parts of Bootstrap yourself.
Configure Bladestrap
Usually this should not be necessary, but if you need to overwrite the default configuration,
create and edit config/bladestrap.php
:
php artisan vendor:publish --tag="bladestrap-config"
Customize views
If you want to customize the views, publish them to resources\views\vendor\bladestrap\components
and edit them to meet your requirements:
php artisan vendor:publish --tag="bladestrap-views"
You may want to delete the views you haven't changed to benefit from package updates automatically.
Usage
The components are placed in the bs
namespace, such that they can be used via:
<x-bs::component-name> <!-- Replace component-name with one of the component names described below -->
Components can be enhanced with additional classes from Bootstrap or your own CSS.
Specifically handled attributes are documented with type annotations in the @props
in the respective Blade template under resources/views/components
.
Alerts
Alerts are of variant alert-info
by default
and can be dismissible (with a close button).
<x-bs::alert>My info alert</x-bs::alert> <x-bs::alert variant="primary">My primary alert</x-bs::alert> <x-bs::alert variant="secondary" :dismissible="true">My dismissible secondary alert</x-bs::alert>
Badges
Badges are of variant badge-primary
default:
<x-bs::badge>My primary badge</x-bs::badge> <x-bs::badge variant="secondary">My secondary badge</x-bs::badge>
Breadcrumb
The breadcrumb container is a <x-bs::breadcrumb>
(typically placed within your layouts/app.blade.php
):
@hasSection('breadcrumbs') <x-bs::breadcrumb container-class="mt-3" class="bg-light"> <x-bs::breadcrumb.item href="{{ route('dashboard') }}">{{ __('Dashboard') }}</x-bs::breadcrumb.item> @yield('breadcrumbs') </x-bs::breadcrumb> @endif
Items can be added via <x-bs::breadcrumb.item :href="route('route-name')">Title</x-bs::breadcrumb.item>
.
Buttons
To create buttons
or button-like links with Bootstrap's btn-*
classes you can use
<x-bs::button>
(becomes a<button>
)- and
<x-bs::button.link>
(becomes an<a>
). Per defaultbtn-primary
is used, you can change that with the variant.
<x-bs::button href="{{ route('my-route') }}" variant="danger">{{ __('Delete') }}</x-bs::button> <x-bs::button.link href="{{ route('my-route') }}">{{ __('My title') }}</x-bs::button.link>
To disable a button or link, just add disabled="true"
which automatically adds the corresponding class
and aria-disabled="true"
as recommended by the Bootstrap documentation.
Button groups
Buttons can be grouped:
<x-bs::button.group> <x-bs::button>Button 1</x-bs::button> <x-bs::button variant="secondary">Button 2</x-bs::button> </x-bs::button.group>
Button toolbars
Button groups can be grouped into a toolbar:
<x-bs:toolbar aria-label="Toolbar with two groups"> <x-bs::button.group aria-label="First group"> <x-bs::button>Button 1</x-bs::button> <x-bs::button>Button 2</x-bs::button> </x-bs::button.group> <x-bs::button.group aria-label="Second group"> <x-bs::button variant="secondary">Button 3</x-bs::button> <x-bs::button variant="secondary">Button 4</x-bs::button> </x-bs::button.group> </x-bs:toolbar>
Dropdowns
Dropdown buttons can be added as follows:
<x-bs::dropdown.button direction="end" variant="secondary"> My button <x-slot:dropdown> <x-bs::dropdown.item href="#">Item 1</x-bs::dropdown.item> <x-bs::dropdown.item href="#">Item 2</x-bs::dropdown.item> </x-slot:dropdown> </x-bs::dropdown.button>
The direction
attribute can be used to set the direction of the dropdown overlay. It defaults to down
.
variant
(default primary
) is inherited from the button component.
Within the <x-slot:dropdown>
you may place headers
and items:
<x-bs::dropdown.header>My header</x-bs::dropdown.header> <x-bs::dropdown.item href="#">Item</x-bs::dropdown.item>
Note that Bootstrap's dropdowns require Popper, which needs to be included separately if you don't use Bootstrap's bootstrap.bundle.min.js
.
Forms
Use <x-bs::form>
to create forms (method defaults to POST
),
any additional attributes passed to the form component will be outputted as well:
<x-bs::form method="PUT" action="{{ route('my-route.update') }}" class="my-3"> <!-- TODO: add form fields and buttons --> </x-bs::form>
Bladestrap will inject an CSRF token field for all methods except GET
automatically.
Bladestrap will also configure method spoofing for PUT
, PATCH
and DELETE
forms.
Types of form fields
Bladestrap has wide support for Bootstrap's form fields.
<x-bs::form.field name="my_field_name" type="text" value="My value">{{ __('My label') }}</x-bs::form.field>
Note that the content of the form field becomes the label. This allows to include icons etc. If you don't want to add a label, don't pass any content:
<x-bs::form.field name="my_field_name" type="text" value="My value"/>
All attributes will be passed to the <input>
, <select>
, <textarea>
- except
- the attributes which start with
container-
(those will be applied to the container for the label and input) - and the attributes which start with
label-
(those will be applied to the label).
The following types are supported as values for the type
attribute:
checkbox
- creates a normal checkbox, requires:options
color
date
datetime-local
*email
file
hidden
- ignores slots for label, hint and input groupmonth
*number
password
radio
- creates a radio, requires:options
range
select
- creates a dropdown (<select>
with<option>
s), requires:options
switch
- creates a toggle switch, requires:options
tel
text
textarea
- creates a<textarea>
time
*url
week
*
The types (marked with *) listed above don't have full browser support.
Options
Radio buttons, checkboxes and selects need a :options
attribute providing an iterable of value/label pairs, e.g.
- an
array
, as in:options="[1 => 'Label 1', 2 => 'Label 2']"
- an
Illuminate\Support\Collection
, such as:options="User::query()->pluck('name', 'id')"
- or
:options="User::query()->pluck('name', 'id')->prepend(__('all'), '')"
- a
Portavice\Bladestrap\Support\Options
which allows to set custom attributes for each option. For checkboxes, radios and switches, custom attributes prefixed withcheck-container-
orcheck-label-
are applied to the.form-check
or.form-check-label
respectively. If labels contain HTML, set:allow-html="true"
.
An Portavice\Bladestrap\Support\Options
can be used to easily create an iterable based on
- an
array
use Portavice\Bladestrap\Support\Options; // Array with custom attributes Options::fromArray( [ 1 => 'One', 2 => 'Two', ], static fn ($optionValue, $label) => [ 'data-value' => $optionValue + 2, ] );
- an
enum
implementing the BackedEnum interfaceuse Portavice\Bladestrap\Support\Options; // All enum cases with labels based on the value Options::fromEnum(MyEnum::class); // ... with labels based on the name Options::fromEnum(MyEnum::class, 'name'); // ... with labels based on the result of the myMethod function Options::fromEnum(MyEnum::class, 'myMethod'); // Only a subset of enum cases Options::fromEnum([MyEnum::Case1, MyEnum::Case2]);
- an
array
orIlluminate\Database\Eloquent\Collection
of Eloquent models (the primary key becomes the value, label must be defined)use Portavice\Bladestrap\Support\Options; // Array of models with labels based on a column or accessor Options::fromModels([$user1, $user2, ...$moreUsers], 'name'); // Collection of models with labels based on a column or accessor Options::fromModels(User::query()->get(), 'name'); // ... with labels based on a Closure Options::fromModels( User::query()->get(), static fn (User $user) => sprintf('%s (%s)', $user->name, $user->id) ); // ... with custom attributes for <option>s using a \Closure defining an ComponentAttributeBag Options::fromModels(User::query()->get(), 'name', static function (User $user) { return (new ComponentAttributeBag([]))->class([ 'user-option', 'inactive' => $user->isInactive(), ]); }); // ... with custom attributes for <option>s using a \Closure defining an array of attributes Options::fromModels(User::query()->get(), 'name', fn (User $user) => [ 'data-title' => $user->title, ]);
Additional options can be prepended/appended to an Options
:
use Portavice\Bladestrap\Support\Options; $options = Options::fromModels(User::query()->get(), 'name') ->sortAlphabetically() // call sort for current options ->prepend('all', '') // adds an option with an empty value before first option ->append('label for last option', 'value') // adds an option after the last option ->prependMany([ // adds options before the first option (value => label) 'value-1' => 'first prepended option', 'value-2' => 'second prepended option', ]);
Radio buttons (allows to select one of multiple values):
<x-bs::form.field name="my_field_name" type="radio" :options="$options" :value="$value">{{ __('My label') }}</x-bs::form.field>
Multiple checkboxes (allows to select multiple values):
<x-bs::form.field id="my_field_name" name="my_field_name[]" type="checkbox" :options="$options" :value="$value">{{ __('My label') }}</x-bs::form.field>
Single checkbox (just one option):
<x-bs::form.field id="my_field_name" type="checkbox" :options="[1 => 'Option enabled']" :value="$value">{{ __('My label') }}</x-bs::form.field> <x-bs::form.field id="my_field_name" type="checkbox" :allow-html="true" :options="Options::one('Option <strong>with HTML</strong> enabled')" :value="$value">{{ __('My label') }}</x-bs::form.field>
Select (allows to select one of multiple values):
<x-bs::form.field name="my_field_name" type="select" :options="$options" :value="$value">{{ __('My label') }}</x-bs::form.field>
Multi-Select (allows to select multiple values):
<x-bs::form.field id="my_field_name" name="my_field_name[]" type="select" multi :options="$options" :value="$value">{{ __('My label') }}</x-bs::form.field>
Disabled, readonly, required
The attributes :disabled
, :readonly
, and :required
accept a boolean value,
e.g. :disabled="true"
or :required="isset($var)"
.
Per default fields with :required="true"
are marked with a *
after the label.
This behavior can be disabled via configuration (for all fields) or with :mark-as-required="false"
(for a single field).
Input groups
To add text at the left or the right of a form field (except checkboxes and radio buttons),
you can use the slots <x-slot:prependText>
and <x-slot:appendText>
which makes an input group:
<x-bs::form.field name="my_field_name" type="number" min="0" max="100" step="0.1"> {{ __('My label') }} <x-slot:prependText>≥</x-slot:prependText> <x-slot:appendText>€</x-slot:appendText> </x-bs::form.field>
By default, the appended/prepended text is wrapped within a <label> class="input-group-text"
associated with the field.
To avoid this, set :container="false"
attribute on the slot which allows to define to add buttons for example:
<x-bs::form.field name="file" type="file"> File <x-slot:appendText> <x-bs::button.link variant="primary" href="test.pdf">Download current file</x-bs::button.link> </x-slot:appendText> </x-bs::form.field>'
Alternatively, an appendText
slot can include a <x-bs::form.field:nested-in-group="true">
:
<x-bs::form.field name="price_from" type="number" min="1" step="1"> <x-slot:prependText>from</x-slot:prependText> Price <x-slot:appendText :container="false"> <x-bs::form.field name="price_until" type="number" min="1" step="1" :nested-in-group="true"> <x-slot:prependText>until</x-slot:prependText> <x-slot:appendText>€</x-slot:appendText> </x-bs::form.field> </x-slot:appendText> </x-bs::form.field>
Hints
<x-slot:hint>
can be used to add a text with custom hints (.form-text
) below the field,
which will be automatically referenced via aria-describedby
by the input:
<x-bs::form.field name="my_field_name" type="text"> {{ __('My label') }} <x-slot:hint>Hint</x-slot:hint> </x-bs::form.field>
Prefill values from query parameters
Setting :from-query="true"
will extract values from the query parameters of the current route.
<x-bs::form.field id="name" name="filter[name]" type="text" :from-query="true">{{ __('Name') }}</x-bs::form.field>
A form with the example field above on a page /my-page?filter[name]=Test
will set "Test" as the prefilled value of the field,
while /my-page
will have an empty value.
To pass default filters applied if no query parameters are set, use ValueHelper::setDefaults
:
use Portavice\Bladestrap\Support\ValueHelper; ValueHelper::setDefaults([ 'filter.name' => 'default', ])
Error messages
All form fields show corresponding error messages automatically if present (server-side validation). If you want to show them independent of a form field, you can use the component directly:
<x-bs::form.feedback name="{{ $name }}"/>
Both <x-bs::form.feedback>
and <x-bs::form.field>
support to use another than the default error bag via the :errors
attribute.
Links
Colored links can be placed via <x-bs::link>
,
the attributes opacity
and opacityHover
define opacity.
<x-bs::link href="{{ route('my-route') }}">Link text</x-bs::link> <x-bs::link href="{{ route('my-route') }}" variant="danger">Link text</x-bs::link> <x-bs::link href="{{ route('my-route') }}" opacity="25">Link text</x-bs::link>
List groups
<x-bs::list>
is a list group, a container for multiple <x-bs::list.item>
.
:flush="true"
enables flush behavior,
:horizontal="true
changes the layout from vertical to horizontal.
Items can be added via <x-bs::list.item>
:
<x-bs::list> <x-bs::list.item>Item 1</x-bs::list.item> <x-bs::list.item :active="true">Item 2</x-bs::list.item> </x-bs::list>
:active="true"
highlights the active item,
:disabled="true"
makes it appear disabled.
Modals
Modals can be created via <x-bs::modal>
with optional slots for title and footer.
Both slots accept additional classes and other attributes.
If you don't want a <h1>
container for the title, change it via container="h2"
etc.
<x-bs::modal.button modal="my-modal">Open modal</x-bs::modal.button> <x-bs::modal id="my-modal"> <x-slot:title>My modal title</x-slot:title> <x-slot:footer> <x-bs::button>Test</x-bs::button> </x-slot:footer> </x-bs::modal>
<x-bs::modal>
supports the following optional attributes:
centered
to center the modal vertically (defaults tofalse
)fade
for the fade effect when opening the modal (defaults totrue
)fullScreen
to force fullscreen (defaults tofalse
, passtrue
to always enforce full screen orsm
to enforce for sizes below the sm breakpoint etc.),scrollable
to enable a vertical scrollbar for long dialog content (defaults tofalse
)staticBackdrop
' to enforce that clicking outside of it does not close the modal (defaults tofalse
)closeButton
sets the variant of the close button in the modal footer (defaults tosecondary
,false
to disable the close button),closeButtonTitle
for the title of the close button (defaults to "Close")
A <x-bs::modal.button modal="my-modal">
opens the modal with the ID my-modal
.
You may pass any additional attributes as known from <x-bs::button>
.
Navigation
<x-bs::nav>
creates a nav container, use container="ol"
to change the container element from the default <ul>
to <ol>
.
Navigation items can be added via <x-bs::nav.item href="{{ route('route-name') }}">Current Page</x-bs::nav.item>
.
A navigation item may open a dropdown if you enabled this by adding a dropdown slot:
<x-bs::nav.item id="navbarUserDropdown"> Dropdown link text <x-slot:dropdown class="dropdown-menu-end"> <!-- dropdown content--> </x-slot:dropdown> </x-bs::list.item>
Usage without Laravel
Bladestrap uses config()
and request()
helpers.
If you want to use Bladestrap without Laravel, you need to define the two helpers in your application,
for example (may need to be adapted to the framework you use):
use Illuminate\Http\Request; use Illuminate\Support\Arr; $configFile = [ 'bladestrap' => require __DIR__ . '/../vendor/portavice/bladestrap/config/bladestrap.php', ]; function config(array|string|null $key, mixed $default = null): mixed { global $configFile; return Arr::get($configFile, $key, $default); } $request = Request::capture(); function request(array|string|null $key = null, mixed $default = null): mixed { global $request; return $key === null ? $request : $request->input($key, $default); }
In addition, you have to do the registrations of the BladestrapServiceProvider
yourself:
use Illuminate\View\Factory; use Portavice\Bladestrap\Macros\ComponentAttributeBagExtension; // Register macros as BladestrapServiceProvider would do. ComponentAttributeBagExtension::registerMacros(); /* @var Factory $viewFactory */ // Add components in bs namespace to your views. $viewFactory->addNamespace('bs', __DIR__ . '/../vendor/portavice/bladestrap/resources/views');