Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Blade Components Laravel Package

ddfsn/blade-components

ddfsn/blade-components is a Laravel package that adds reusable Blade components for building clean, consistent UI faster. Drop-in components cut down repetitive markup, keep your views tidy, and make interfaces easier to maintain across your app.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish.

composer require ddfsn/blade-components
php artisan vendor:publish --provider="DistortedFusion\BladeComponents\BladeComponentsServiceProvider"

Publishing is the important step — you want the config/blade-components.php and the package views copied into resources/views/vendor/blade-components so you can read and edit every template the package renders. Without publishing you are working blind against markup buried in vendor/.

Where to look first, in order:

  1. README.md — the component inventory and any required CSS/JS setup (this package is presentation-only; it does not ship a JS runtime, so it assumes your app already has Tailwind and, for interactive pieces like modals/dropdowns, Alpine or Livewire).
  2. config/blade-components.php — component prefix, enabled components, and the theme tokens. As of 1.6.3 the theme surface includes ThemeVariable::BACKGROUND, ThemeVariable::SIDEBAR, and ThemeVariable::HEADER (the latter two default to BACKGROUND). This file tells you the naming convention (<x-button> vs <x-bc::button>) faster than grepping.
  3. src/View/Components/ — the PHP classes. This is the real API: props, defaults, attribute merging, and slot names live here, not in the docs. src/ThemeVariable.php (or equivalent enum) is where the theme token names are actually enumerated.
  4. resources/views/components/ — the markup and the Tailwind class strings you will inevitably need to override.

First use case. Pick your most copy-pasted chunk of markup — usually a form input with label, error state, and old() handling — and replace it. Concretely:

{{-- before --}}
<div class="mb-4">
    <label for="email" class="block text-sm font-medium text-gray-700">Email</label>
    <input id="email" name="email" type="email"
           value="{{ old('email') }}"
           class="mt-1 block w-full rounded-md border-gray-300 @error('email') border-red-500 @enderror">
    @error('email') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>

{{-- after --}}
<x-input name="email" type="email" label="Email" />

If that single substitution saves you 8 lines per field across a 30-field app, you have validated the package in an afternoon. If it doesn't — because your design doesn't match the package's defaults and you spend the afternoon overriding classes — you have learned that cheaply.

Do one page, not the whole app, before committing.

Implementation Patterns

Alias into your own namespace rather than calling the vendor directly. This is the single highest-leverage pattern for a low-adoption package: it caps your migration cost to a handful of files if you ever swap it out or fork it.

{{-- resources/views/components/ui/button.blade.php --}}
<x-blade-components::button {{ $attributes->merge(['class' => 'font-semibold']) }}>
    {{ $slot }}
</x-blade-components::button>

Now app code says <x-ui.button> everywhere and the vendor name appears in exactly one file. When you find a bug in the package's markup, you fix it in your alias, not in vendor/.

Override views, don't fork the package. Published views under resources/views/vendor/blade-components take precedence over the package's own. Use this for structural changes (different wrapper elements, adding wire:model support) and keep class-level tweaks in your aliases. Re-publishing with --force will clobber your edits — see Gotchas.

Drive variants from config, not from string literals. If the package exposes a theme/variant config, wire your app's semantic names to it once:

// config/blade-components.php
'variants' => [
    'primary'   => 'bg-indigo-600 hover:bg-indigo-700 text-white',
    'danger'    => 'bg-red-600 hover:bg-red-700 text-white',
    'secondary' => 'bg-white border border-gray-300 text-gray-700',
],

Then <x-ui.button variant="danger"> reads the same in every context, and a rebrand is a one-file change. This is the main reason to prefer a component library over a CSS framework in the first place — make sure you actually get that benefit.

Theme variables are the right hook for app-shell chrome (1.6.3+). ThemeVariable::BACKGROUND, ThemeVariable::SIDEBAR, and ThemeVariable::HEADER give you a named surface for layout chrome instead of hand-rolling classes into your layout Blade. Set them once and let the package's sidebar/header components inherit:

// config/blade-components.php
'theme' => [
    \DistortedFusion\BladeComponents\ThemeVariable::BACKGROUND->value => 'bg-slate-50 dark:bg-slate-900',
    \DistortedFusion\BladeComponents\ThemeVariable::SIDEBAR->value    => 'bg-slate-100 dark:bg-slate-800',
    \DistortedFusion\BladeComponents\ThemeVariable::HEADER->value     => 'bg-white dark:bg-slate-950 border-b border-slate-200 dark:border-slate-800',
],

Because SIDEBAR and HEADER default to BACKGROUND, leaving them unset is a legitimate one-line theme: change BACKGROUND and all three surfaces move together. Only override them individually when you actually want the chrome to diverge. Verify the enum case names and the config key against src/ThemeVariable.php in the version you install — this is the part of the config most likely to move between minor releases.

Form integration is the workflow that pays daily. Wrap the package's input in your own component so validation state is never re-implemented:

@props(['name', 'label' => null])

<div {{ $attributes->only('class')->merge(['class' => 'mb-4']) }}>
    @if ($label)
        <label for="{{ $name }}" class="block text-sm font-medium text-gray-700">{{ $label }}</label>
    @endif

    <x-blade-components::input
        :name="$name"
        :id="$name"
        {{ $attributes->except(['class', 'label']) }}
        @class(['border-red-500' => $errors->has($name)])
    />

    @error($name)
        <p class="mt-1 text-sm text-red-600">{{ $message }}</p>
    @enderror
</div>

Note the careful only/except on the attribute bag — this is where most integration bugs appear. The outer div should not swallow name or value.

Lean on breadcrumb slots rather than rebuilding breadcrumbs (1.6.3+). The breadcrumb components now expose slots. If you have an app-side breadcrumb wrapper that concatenated markup because there was no slot to fill, delete it and compose the vendor slots directly — that workaround is now dead weight, and it will drift from the package's markup:

<x-breadcrumb>
    <x-slot:...> {{-- check the component class for slot names --}}
        {{ $whatever }}
    </x-slot>
</x-breadcrumb>

Read the breadcrumb class in src/View/Components/ first — "added slots" does not tell you which slots or their names, and slot names are exactly the kind of thing that isn't in a changelog.

Livewire compatibility is a design constraint, not an afterthought. Because these are plain Blade components, they work inside Livewire, but only if attributes pass through. Test one component with wire:model early:

<x-ui.input name="email" wire:model.blur="email" label="Email" />

If the attribute doesn't reach the <input>, the component is doing $attributes->merge() in a way that drops unprefixed attributes — check the class and fix it in your alias layer.

Layer compositions for repeated UI, don't nest vendor components in app views. Build <x-ui.card> that internally composes the package's card header/body/footer so views stay flat and readable, and so you can restyle all cards from one place.

Test the components you own. A cheap component test catches broken slots and attribute passthrough without a browser:

it('renders a labelled input', function () {
    $this->blade('<x-ui.input name="email" label="Email" />')
        ->assertSee('Email')
        ->assertSee('name="email"', false);
});

Gotchas and Tips

Re-publishing overwrites your edits. vendor:publish --force replaces everything under resources/views/vendor/blade-components. Publish a tag once, then version-control those views and never re-publish blindly. If the package adds new components in an upgrade, diff the vendor folder against yours rather than forcing.

Tailwind will purge your component classes. This is the number-one cause of "the component renders but looks unstyled." Tailwind only scans configured content paths, and vendor Blade files are not in ./resources/** by default. Either add the published views path (safer) or the vendor path:

// tailwind.config.js
content: [
  './resources/**/*.blade.php',
  './resources/views/vendor/blade-components/**/*.blade.php',
],

Prefer adding the published path — then class changes you make locally are always picked up. Note this bites harder with theme variables: putting a token class string in config/blade-components.php means it lives in a .php file Tailwind never scans, so those classes get purged even though the config is "correct." Either safelist them or keep token values in classes you already use elsewhere.

No JS ships with it. Anything interactive (modals, dropdowns, tabs) relies on Alpine, Livewire, or your own handlers. Check the README's required attributes; a modal that "doesn't work" is usually a missing x-data or a script tag you never added. Don't assume the package is broken on first failure.

Dark-mode flicker on buttons is a package bug, not your code — and 1.6.3 fixes it. Prior releases let transitions animate during appearance changes, so toggling light/dark produced a visible flash. If you still see flicker after upgrading, the cause is almost always a published button view from an older version that you never updated: your local copy predates the fix. Diff your resources/views/vendor/blade-components against the vendor folder before doing any diagnosis in your own CSS.

SIDEBAR/HEADER inheriting BACKGROUND surprises people in both directions. Setting BACKGROUND silently restyles sidebar and header too — good when that's what you want, confusing when you set it for the page body and wonder why the nav changed. Conversely, if you override BACKGROUND but your app layout hardcodes nav classes, you'll see a mismatch: the package chrome moves, your layout doesn't.

Class-merge conflicts with tailwind-merge. If you run the package's classes through a merge helper, be aware that the vendor may use arbitrary or non-standard class strings that the merger doesn't recognize as conflicting (e.g. custom btn-* utilities). Result: both classes present, last-one-wins by CSS specificity, and unpredictable styling. Debug by inspecting the final class attribute in the rendered HTML, not by reading the template.

Component names can collide with your app components. An app-level <x-button> shadows a package <x-button> depending on registration order. Adopt a prefix (ui., app. or the package's own configured prefix) from day one — retrofitting a prefix across a live app is tedious.

Low adoption means you are the QA team. 27 stars and zero dependents means edge cases in your design system are unlikely to be pre-solved, and a breaking change can land in a minor release without anyone else noticing. Pin the version ("ddfsn/blade-components": "^1.6" with a committed lockfile) and read the diff before upgrading. Skim src/View/Components/ and src/ThemeVariable.php after each bump — that's where breaking changes appear, and new enum cases land there first.

Plan the exit before you need it. The alias-namespace pattern above is the exit. Additionally, keep your published views committed and consider vendoring the package's templates directly into resources/views/components/ui/ if you find yourself rewriting more than about a third of them — at that point you own it either way, and dropping the dependency is cleaner than fighting it.

Debugging checklist, in order:

  1. php artisan view:clear && php artisan optimize:clear — stale compiled Blade is the most common false alarm after editing published views.
  2. Confirm which file is actually rendering: php artisan view:cache then inspect storage/framework/views. If your edit isn't there, the publish path or component name is wrong.
  3. Dump the attribute bag from inside your alias with {{ $attributes }} to see exactly what's arriving.
  4. Check config/blade-components.php after upgrades — a renamed config key silently falls back to a default, which looks like "the component ignored my setting." Theme token renames are the likeliest culprit here.

Extension points worth knowing: the config file (theme tokens — BACKGROUND/SIDEBAR/HEADER — plus variants, prefix, toggles), published views (structural overrides), component slots (expanded in 1.6.3 for breadcrumbs), and subclassing a component's PHP class if it exposes settable properties. Subclassing is the most fragile option — the class is not a published API surface — so prefer view overrides and alias wrappers, and only subclass if the README explicitly documents it.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core