zairakai / laravel-blade-components
Collection of reusable Blade components for forms, layouts, navigation and UI elements with Laravel integration
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Forks: 0
pkg:composer/zairakai/laravel-blade-components
Requires
- php: ^8.3
- laravel/framework: ^10.0|^11.0
Requires (Dev)
- driftingly/rector-laravel: ^2.1
- ergebnis/composer-normalize: ^2.49
- friendsofphp/php-cs-fixer: ^3.64
- larastan/larastan: ^3.9
- laravel/pint: ^1.27
- nunomaduro/phpinsights: ^2.13
- orchestra/testbench: ^9.0 || ^10.0
- phpmetrics/phpmetrics: ^2.9
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^10.0 || ^11.0
- rector/rector: ^2.3
- zairakai/laravel-dev-tools: ^3.0
Suggests
- ergebnis/composer-normalize: Automated composer.json normalization
This package is auto-updated.
Last update: 2026-02-23 15:27:07 UTC
README
62 reusable Blade components for Laravel — forms, layout, content and media — with built-in i18n (21 locales), accessibility attributes, and zero CSS dependencies.
Table of Contents
- Requirements
- Installation
- Configuration
- Publishing assets
- Components
- Helpers
- Internationalization
- Testing
- Contributing
- License
Requirements
| Dependency | Version |
|---|---|
| PHP | ^8.3 |
| Laravel | ^10.0 \| ^11.0 |
Installation
composer require zairakai/laravel-blade-components
The service provider is auto-discovered. No additional setup required.
Configuration
The package ships with sensible defaults. Publish the config to override them:
php artisan vendor:publish --tag=zairakai-config
This creates config/blade-components.php:
return [
'password' => [
'min_characters' => 8, // Minimum length for <x-zk-password>
],
'email' => [
'pattern' => '[^@]+@[^@]+\.[a-zA-Z]{2,}', // Regex for <x-zk-email>
],
'select' => [
'icon_after' => 'keyboard_arrow_down', // Trailing icon for <x-zk-select>
],
];
Publishing assets
| Tag | Contents | Destination |
|---|---|---|
zairakai-components | Blade views per category | resources/views/vendor/zairakai/ |
zairakai-lang | Translation files (21 locales) | lang/vendor/zairakai/ |
zairakai-config | Configuration file | config/blade-components.php |
zairakai-all | All of the above | — |
# Publish everything at once
php artisan vendor:publish --tag=zairakai-all
# Or selectively
php artisan vendor:publish --tag=zairakai-components
php artisan vendor:publish --tag=zairakai-lang
php artisan vendor:publish --tag=zairakai-config
Components
All public components use the zk- prefix. Internal cross-component references
(used inside Blade templates) also work without prefix — they are registered
automatically.
Content components
| Component | Tag | Description |
|---|---|---|
| Blockquote | <x-zk-blockquote> | Styled <blockquote> with optional cite |
| Heading | <x-zk-heading> | h1–h6 with level prop |
| Link | <x-zk-link> | Anchor with route resolution, icons, active state |
| List | <x-zk-list> | Recursive ul/ol supporting links, images, text |
| MSR | <x-zk-msr> | Material Symbols / Rounded icon wrapper |
| Paragraph | <x-zk-paragraph> | <p> with id and class support |
Link
{{-- Simple href --}}
<x-zk-link href="https://example.com" target="_blank" rel="noopener">
Visit site
</x-zk-link>
{{-- Named route — adds class="active" when current --}}
<x-zk-link :route="'dashboard'" :routeParams="['id' => 1]">
Dashboard
</x-zk-link>
{{-- With Material Symbols icon --}}
<x-zk-link route="profile" msr="person">
My Profile
</x-zk-link>
List
<x-zk-list :items="[
['type' => 'href', 'href' => '/home', 'label' => 'Home'],
['type' => 'route', 'route' => 'dashboard', 'label' => 'Dashboard'],
['type' => 'text', 'label' => 'Plain text item'],
[
'type' => 'href', 'href' => '/parent', 'label' => 'Parent',
'children' => [
'ordered' => false,
'children' => [
['type' => 'href', 'href' => '/child', 'label' => 'Child'],
],
],
],
]" />
Heading
<x-zk-heading :level="1">Page Title</x-zk-heading>
<x-zk-heading :level="2" class="section-title">Section</x-zk-heading>
Form components
| Component | Tag | Description |
|---|---|---|
| Input | <x-zk-input> | Universal input (all HTML types) with label, icons, supporting text |
| Textarea | <x-zk-textarea> | Textarea with label, character counter |
| Select | <x-zk-select> | Select dropdown with optgroups, default option, icons |
| Checkbox | <x-zk-checkbox> | Checkbox with optional label wrapper |
| Radio | <x-zk-radio> | Radio button |
| Switch | <x-zk-switch> | Toggle switch (wraps checkbox) |
| Button | <x-zk-button> | <button> with field wrapper, icons |
| Submit | <x-zk-submit> | Submit button (wraps button) |
| Reset | <x-zk-reset> | Reset button (wraps button) |
| Form | <x-zk-form> | <form> with auto CSRF, spoofing, enctype detection |
| Label | <x-zk-label> | <label> with icon support |
| Field | <x-zk-field> | Field wrapper div with error class |
| Fieldset | <x-zk-fieldset> | <fieldset> with legend before/after slot |
| Additional | <x-zk-additional> | Supporting text + character counter |
<x-zk-email> | Email input with configurable pattern | |
| Password | <x-zk-password> | Password with min-length pattern |
| Date | <x-zk-date> | Date input |
| Datetime | <x-zk-datetime> | Datetime-local input |
| Time | <x-zk-time> | Time input |
| Week | <x-zk-week> | Week input |
| Month | <x-zk-month> | Month input |
| Number | <x-zk-number> | Number input |
| Range | <x-zk-range> | Range slider |
| Color | <x-zk-color> | Color picker |
| File | <x-zk-file> | File upload input |
| Search | <x-zk-search> | Search input |
| Tel | <x-zk-tel> | Telephone input |
| URL | <x-zk-url> | URL input |
| Hidden | <x-zk-hidden> | Hidden input |
| Datalist | <x-zk-datalist> | <datalist> element |
Input
{{-- Basic --}}
<x-zk-input name="username" label="Username" required />
{{-- With icons, prefix and supporting text --}}
<x-zk-input
name="amount"
label="Amount"
type="number"
prefix="€"
iconAfter="euro"
supportingText="Minimum order: €10"
:min="10"
/>
{{-- Without field wrapper (bare input) --}}
<x-zk-input name="search" type="search" :field="false" placeholder="Search…" />
Select
<x-zk-select
name="country"
label="Country"
:options="$countries" {{-- Collection or array --}}
:selected="$user->country"
required
labelBefore
/>
{{-- With optgroups --}}
<x-zk-select
name="category"
:options="[
'Fruits' => ['apple' => 'Apple', 'banana' => 'Banana'],
'Vegs' => ['carrot' => 'Carrot'],
]"
/>
Form
{{-- POST with CSRF auto-injected --}}
<x-zk-form route="profile.update" method="PUT">
<x-zk-input name="name" label="Full name" :value="$user->name" required />
<x-zk-submit>Save</x-zk-submit>
</x-zk-form>
{{-- File upload — enctype detected automatically --}}
<x-zk-form route="avatar.store">
<x-zk-file name="avatar" label="Profile picture" accept="image/*" />
<x-zk-submit>Upload</x-zk-submit>
</x-zk-form>
Password
{{-- Uses config('blade-components.password.min_characters', 8) --}}
<x-zk-password name="password" label="Password" required />
{{-- Override minimum --}}
<x-zk-password name="password" label="Password" :min="12" required />
Checkbox & Switch
<x-zk-checkbox name="terms" label="I accept the terms" required />
<x-zk-switch name="notifications" label="Email notifications" :checked="$user->notifications" />
Fieldset
<x-zk-fieldset legend="Shipping address">
<x-zk-input name="address" label="Street" required />
<x-zk-input name="city" label="City" required />
</x-zk-fieldset>
Layout components
| Component | Tag | Description |
|---|---|---|
| Header | <x-zk-header> | <header> semantic element |
| Footer | <x-zk-footer> | <footer> semantic element |
| Main | <x-zk-main> | <main> semantic element |
| Nav | <x-zk-nav> | <nav> with optional role |
| Section | <x-zk-section> | <section> auto-wrapped in container |
| Article | <x-zk-article> | <article> semantic element |
| Aside | <x-zk-aside> | <aside> semantic element |
| Container | <x-zk-container> | <div class="container"> |
| Wrapper | <x-zk-wrapper> | <div class="wrapper"> |
| Grid | <x-zk-grid> | CSS grid wrapper with columns prop |
| Grid Item | <x-zk-grid-item> | Grid cell |
| Row | <x-zk-row> | Flex row |
| Column | <x-zk-column> | Flex/grid column with col prop |
| Breadcrumb | <x-zk-breadcrumb> | <nav> breadcrumb with structured items |
| Tabs | <x-zk-tabs> | Tabbed interface |
| Pagination | <x-zk-pagination> | Page navigation |
Nav
<x-zk-nav role="navigation" class="main-nav">
<x-zk-link route="home">Home</x-zk-link>
<x-zk-link route="about">About</x-zk-link>
</x-zk-nav>
Breadcrumb
<x-zk-breadcrumb :items="[
['label' => 'Home', 'href' => '/'],
['label' => 'Products', 'href' => '/products'],
['label' => 'Laptop', 'aria-current' => 'page'],
]" />
Grid
<x-zk-grid :columns="3" class="product-grid">
@foreach ($products as $product)
<x-zk-grid-item>{{ $product->name }}</x-zk-grid-item>
@endforeach
</x-zk-grid>
Pagination
<x-zk-pagination :currentPage="$page" :totalPages="$total" />
Tabs
<x-zk-tabs :tabs="[
['title' => 'Overview', 'content' => 'Tab 1 content'],
['title' => 'Details', 'content' => 'Tab 2 content'],
['title' => 'Reviews', 'content' => 'Tab 3 content'],
]" />
Media components
| Component | Tag | Description |
|---|---|---|
| Image | <x-zk-image> | <img> with srcset, loading, decoding, fetchpriority |
| Video | <x-zk-video> | <video> with sources and tracks |
| Audio | <x-zk-audio> | <audio> with sources and tracks |
| Figure | <x-zk-figure> | <figure> wrapper |
| Figcaption | <x-zk-figcaption> | <figcaption> |
| Canvas | <x-zk-canvas> | <canvas> with width/height |
| Iframe | <x-zk-iframe> | <iframe> with sandbox, allow |
| Object | <x-zk-object> | <object> element |
| Source | <x-zk-source> | <source> element |
| Track | <x-zk-track> | <track> subtitle/caption element |
Image
<x-zk-image
src="/img/hero.webp"
alt="Hero image"
width="1200"
height="600"
loading="lazy"
decoding="async"
fetchpriority="high"
srcset="/img/hero-sm.webp 640w, /img/hero.webp 1200w"
sizes="(max-width: 640px) 100vw, 1200px"
/>
Video
<x-zk-video
:sources="[
['src' => '/video/demo.webm', 'type' => 'video/webm'],
['src' => '/video/demo.mp4', 'type' => 'video/mp4'],
]"
:tracks="[
['src' => '/subs/en.vtt', 'kind' => 'subtitles', 'srclang' => 'en', 'label' => 'English'],
]"
controls
poster="/img/poster.webp"
/>
{{-- Single source shorthand --}}
<x-zk-video sources="/video/demo.mp4" controls />
Audio
<x-zk-audio
:sources="[
['src' => '/audio/track.ogg', 'type' => 'audio/ogg'],
['src' => '/audio/track.mp3', 'type' => 'audio/mpeg'],
]"
controls
/>
Figure with caption
<x-zk-figure>
<x-zk-image src="/img/chart.svg" alt="Sales chart" />
<x-zk-figcaption>Q4 2024 sales by region</x-zk-figcaption>
</x-zk-figure>
Helpers
Three static utility methods are available via BladeHelpers, used internally by the components and accessible in your own Blade views:
| Method | Signature | Description |
|---|---|---|
getOldValue | BladeHelpers::getOldValue(?string $name, mixed $value): mixed | Returns old($name) if available, fallback to $value |
routeIs | BladeHelpers::routeIs(string $route): bool | Checks if the current route matches the given name |
isValidMimeType | BladeHelpers::isValidMimeType(string $type): bool | Validates a MIME type string format |
@php
use Zairakai\LaravelBladeComponents\BladeHelpers;
$value = BladeHelpers::getOldValue('email', $user->email);
@endphp
@if(BladeHelpers::routeIs('dashboard'))
{{-- current route is "dashboard" --}}
@endif
{{-- Validate MIME type --}}
{{-- BladeHelpers::isValidMimeType('image/png') → true --}}
{{-- BladeHelpers::isValidMimeType('not-a-mime') → false --}}
Internationalization
Translation files ship for 21 locales out of the box:
ar · cs · da · de · en · es · fi · fr · it · ja · ko · nl · no · pl · pt · ro · ru · sv · tr · uk · zh
The package uses the zairakai:: namespace internally. Published views use
the same namespace until you override the translation files.
# Publish translations to override
php artisan vendor:publish --tag=zairakai-lang
Translated keys:
// lang/vendor/zairakai/en/layout.php
return [
'medias' => [
'video' => 'Your browser does not support the video element.',
'audio' => 'Your browser does not support the audio element.',
],
'select' => [
'default' => '-- Select an option --',
],
];
Testing
# Run test suite
composer test
# With coverage report
composer test:coverage
# Run BATS shell tests
bats tests/bats/unit/package-structure.bats
bats tests/bats/integration/composer-scripts.bats
# Full quality pipeline
make quality
Contributing
- Fork the repository on GitLab
- Create a feature branch:
git checkout -b feat/my-component - Ensure
make qualitypasses (PHPStan max, Pint, Rector, ShellCheck) - Commit following Conventional Commits:
feat(scope): description - Open a Merge Request
License
MIT — see LICENSE for details.
Support
- Discord — community discussions (🖥️・Developers role)
- Report issues
- Patreon / Twitch
Built by Stanislas Poisson