wangaz/laravel-semantic-ui-css
wangaz/laravel-semantic-ui-css integrates Semantic UI CSS into Laravel, making it easy to publish, configure, and load Semantic UI stylesheets and assets in your Blade views and frontend build.
Install and publish the assets
composer require wangaz/laravel-semantic-ui-css
php artisan vendor:publish --provider="Wangaz\SemanticUiCss\SemanticUiCssServiceProvider"
If you're on Laravel 5.5+ the service provider is auto-discovered. On Laravel ≤5.4 (which this package was built for), register it manually in config/app.php:
'providers' => [
Wangaz\SemanticUiCss\SemanticUiCssServiceProvider::class,
],
Publishing copies the compiled Semantic UI CSS (semantic.min.css) and its themes/default/assets/fonts (Icons font) into public/. Confirm what actually landed before writing any markup:
php artisan vendor:publish --list # find the exact tag
ls public/ # note the real folder name (e.g. public/semantic or public/vendor/semantic)
Where to look first
public/ — the published path is the single source of truth for your <link> href.vendor/wangaz/laravel-semantic-ui-css/src/) — it's tiny; read publishes() to see source → destination mappings and tags.vendor/wangaz/laravel-semantic-ui-css/ for the bundled Semantic version (2.x era) — this determines which class names and components you can rely on.First use case: a login screen
Add the stylesheet once in your base layout:
{{-- resources/views/layouts/app.blade.php --}}
<link rel="stylesheet" href="{{ asset('semantic/semantic.min.css') }}">
Then the form is pure Semantic UI classes — no build step, no config:
<form class="ui form" method="POST" action="{{ route('login') }}">
@csrf
<div class="field @error('email') error @enderror">
<label>E-mail</label>
<input type="email" name="email" value="{{ old('email') }}" placeholder="[email protected]">
</div>
<div class="field @error('password') error @enderror">
<label>Password</label>
<input type="password" name="password">
</div>
<button class="ui primary button" type="submit">Sign in</button>
</form>
That's the whole loop: publish once, asset() once, then write Semantic class names.
Wrap the asset in a layout partial, not per-view
Keep the <link> in one place and let every view extend it. Because the package ships a single compiled stylesheet, there's nothing to concatenate — just load it before your own overrides:
<link rel="stylesheet" href="{{ asset('semantic/semantic.min.css') }}">
<link rel="stylesheet" href="{{ asset('css/app.css') }}"> {{-- your overrides, always last --}}
Abstract Semantic classes into Blade components
Semantic UI is verbose (ui secondary pointing menu, ui inverted segment). If you're on Laravel 7+, wrap the recurring ones so class churn happens in one file:
{{-- resources/views/components/ui/button.blade.php --}}
<button {{ $attributes->merge(['class' => 'ui ' . ($variant ?? 'basic') . ' button']) }}>
{{ $slot }}
</button>
<x-ui.button variant="primary">Save changes</x-ui.button>
This also gives you an escape hatch later if you migrate from Semantic to Tailwind/Bootstrap — you change the component, not 200 views.
Menus that reflect the current route
Semantic's active class maps cleanly onto Laravel's route helpers:
<div class="ui secondary pointing menu">
<a class="item {{ request()->routeIs('dashboard') ? 'active' : '' }}"
href="{{ route('dashboard') }}">Dashboard</a>
<a class="item {{ request()->routeIs('projects.*') ? 'active' : '' }}"
href="{{ route('projects.index') }}">Projects</a>
</div>
Flash messages and validation errors as Semantic messages
@if (session('status'))
<div class="ui positive message">
<i class="close icon"></i>
<div class="header">{{ session('status') }}</div>
</div>
@endif
@error('email')
<div class="ui pointing red basic label">{{ $message }}</div>
@enderror
Layouts: use Semantic's grid, but don't fight it
ui stackable grid + column is the workhorse for admin screens and gives you mobile behaviour for free. Reserve custom CSS for things Semantic genuinely doesn't cover; bolting flexbox hacks onto .ui.grid tends to break its negative-margin gutters.
Pagination needs a custom view
Laravel's default paginator markup is Tailwind/Bootstrap-shaped and will render as an unstyled list on a Semantic page. Publish the pagination views and rewrite pagination::semantic-ui, then set it as your default:
// app/Providers/AppServiceProvider.php
use Illuminate\Pagination\Paginator;
public function boot(): void
{
Paginator::defaultView('pagination::semantic-ui');
}
Use ui pagination menu / item / active item / disabled item classes inside that view.
If you use Mix or Vite, treat these assets as static
Don't try to @vite the published CSS — it lives in public/ and is already minified. At most, copy it into your build for fingerprinting:
// vite.config.js
import { defineConfig } from 'vite';
// ...or simply leave it alone and reference it with asset()
The pragmatic answer on modern stacks: keep asset('semantic/semantic.min.css') and skip the bundler entirely for this file.
This is CSS only — interactive components won't work out of the box. The package name is literal. Dropdowns, modals, tabs, accordions, and the searchable select all need Semantic UI's JavaScript, which requires jQuery. If you need behaviour, either include semantic.min.js plus jQuery from a CDN, or implement the behaviour with Alpine.js/Livewire against Semantic's expected class toggles (active, visible, .ui.modal). Plan for this early — it's the most common surprise.
Check the published font path before shipping. Semantic's icon font is loaded relative to the CSS via themes/default/assets/fonts/*. If your publish destination flattens the directory, every <i class="user icon"> becomes an empty box. Verify with devtools → Network that outline-icons.woff2 / icons.woff2 return 200. If they 404, either restore the directory structure or override @font-face in your own CSS.
The package is essentially frozen (last release 2017). Expect Composer conflicts on Laravel 9+. If composer require fails on illuminate/support constraints, don't force it into composer.json — the honest options are (a) vendor the CSS files into resources/ or public/ manually and drop the dependency, or (b) fork the package and widen the constraint. Since all it does is copy files, vendoring loses you almost nothing and removes an unmaintained dependency from your lockfile.
Know what vendor:publish will overwrite. Published assets are static copies — they do not track package updates, and re-publishing with --force will silently clobber any hand-edits you made in public/. Never edit the published Semantic files. Put every tweak in your own public/css/app.css loaded afterwards.
No theming, no variables. There's no Node/Sass pipeline here, so Semantic's theme.config and site.variables customisation path is unavailable. You get the default theme. Override at the CSS level with specificity (body .ui.primary.button { ... }) or, if you need real theming, this package is the wrong tool — use the npm build.
Decide whether to commit public/ assets. Because publishing is a one-time, non-idempotent step, either commit the published files (simplest for CI/deploy, guarantees parity across environments) or make vendor:publish --force part of your deploy script. Mixing the two — committed assets plus a deploy that republishes — creates confusing overwrite bugs.
Versioning/cache-busting is your job. asset() won't fingerprint these files. Append a manual query string or a config value when you upgrade Semantic so browsers don't serve a stale stylesheet:
<link rel="stylesheet" href="{{ asset('semantic/semantic.min.css') }}?v={{ config('app.asset_version') }}">
Extension points are limited — and that's fine. The provider only publishes files; there are no config knobs, Blade directives, or view components shipped. Your extension surface is: your own layout, your own CSS loaded last, and Blade components wrapping Semantic classes. Debug issues in that order — a class that "isn't working" is almost always being overridden by app.css or the wrong published path, not a package bug. Run php artisan view:clear and hard-reload before chasing anything deeper.
How can I help you explore Laravel packages today?