Laravel 11 + My Own CSS: What I Did, What I Loved, What Bugged Me

Hi, I’m Kayla. I built a small client dashboard with Laravel 11 last month. It needed clean buttons, a simple card look, and a calm blue theme. I wanted full control, so I wrote my own CSS. Was it smooth? Mostly. And when it wasn’t, I scribbled notes and fixed it. Coffee helped.

What I started with

Fresh Laravel 11. Vite handles the assets. Nothing fancy.
If you ever want to peek under the hood, the Laravel’s official documentation on asset bundling with Vite walks through every knob and switch.

  • I ran: composer create-project laravel/laravel example-app
  • Then: npm install and npm run dev
  • I used the default layout and added my styles with Vite

You know what? The hot reload felt great. Save, switch tabs, done.

If you want the blow-by-blow setup, I jotted a separate log of the process in this detailed Laravel 11 + custom CSS diary.

How I wired my CSS

I tried three ways. All worked. I’ll show you what stuck for me.

If you just need a quick, lightweight navigation bar to drop into any of these approaches, grab one of the pure-CSS snippets from CSS Menu Tools and paste it in—you’ll be tweaking colors in seconds.

1) Put styles in the default app.css

File: resources/css/app.css

/* Theme */
:root {
  --brand: #2563eb; /* bright blue */
  --brand-dark: #1e40af;
  --ink: #0f172a;   /* deep slate */
  --paper: #ffffff;
}

/* Base */
html, body {
  background: var(--paper);
  color: var(--ink);
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  line-height: 1.5;
}

/* Buttons */
.btn {
  display: inline-flex;
  gap: .5rem;
  align-items: center;
  padding: .6rem 1rem;
  border-radius: .5rem;
  border: 1px solid transparent;
  cursor: pointer;
  transition: background .15s ease, transform .05s ease;
}

.btn-primary {
  background: var(--brand);
  color: #fff;
}

.btn-primary:hover {
  background: var(--brand-dark);
  transform: translateY(-1px);
}

/* Cards */
.card {
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: .75rem;
  box-shadow: 0 2px 6px rgba(15, 23, 42, .05);
  padding: 1rem;
}

/* Simple grid */
.grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr;
}

@media (min-width: 768px) {
  .grid-2 { grid-template-columns: 1fr 1fr; }
}

Then I made sure Blade pulled it in.

File: resources/views/layouts/app.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>{{ $title ?? 'Dashboard' }}</title>
  @vite(['resources/css/app.css', 'resources/js/app.js'])
  @stack('styles') {{-- for page-only styles --}}
</head>
<body class="antialiased">
  <main class="container">
    {{ $slot ?? '' }}
    @yield('content')
  </main>
</body>
</html>

That’s it. Vite served it. My buttons showed up blue. I smiled.

2) Split out a custom.css and import it

I wanted smaller files. So I made a second file.

File: resources/css/custom.css

/* Login page tweaks */
.login-box {
  max-width: 420px;
  margin: 6rem auto;
}

.help-text {
  font-size: .85rem;
  color: #64748b;
}

Then I linked it from app.css.

File: resources/css/app.css

@import './custom.css';

/* rest of app.css stays the same */

This kept my general theme in app.css and page bits in custom.css. Nice and tidy.

3) Page-only CSS with Blade stacks

For one-off polish, I used a stack. No new file. Quick and a little sneaky.

In the layout head I had:

@stack('styles')

On my stats page:

@push('styles')
<style>
.badge {
  display: inline-block;
  background: #fef3c7;
  color: #92400e;
  border: 1px solid #f59e0b;
  padding: .2rem .5rem;
  border-radius: .375rem;
}
</style>
@endpush

I wouldn’t do this for big chunks. But for one badge? Perfect.

Real examples from my build

I made a sales card with a little hover. It felt lively but not loud.

Blade:

<div class="card">
  <h2 class="mb-2">Monthly Sales</h2>
  <p class="text-muted">$42,310</p>
  <button class="btn btn-primary">Export CSV</button>
</div>

CSS (in app.css):

.card:hover {
  box-shadow: 0 8px 18px rgba(15, 23, 42, .08);
}
.text-muted { color: #6b7280; }
.mb-2 { margin-bottom: .5rem; }

I also added a tiny dark mode, because late-night fixes are real.

@media (prefers-color-scheme: dark) {
  :root {
    --paper: #0b1220;
    --ink: #e5e7eb;
  }
  .card {
    background: #0f172a;
    border-color: #1f2937;
    box-shadow: none;
  }
  .btn-primary {
    background: #1e3a8a;
  }
}

If you use Tailwind with Laravel 11

I did on a second page. The Tailwind CSS official guide for installing Tailwind with Laravel lays out the exact install and configuration steps. Tailwind’s base styles can override your stuff. The fix for me was order. I kept my custom CSS after Tailwind imports. In app.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

/* My overrides last */
.btn-primary { background: var(--brand); }

If something still lost, I used a class with higher weight, not !important. Names like .btn.btn-primary beat a single class most times.
I even went down the rabbit hole of recreating Tailwind’s utility system myself—here’s how that experiment unfolded.

While working on another Laravel side-project, I had to theme a privacy-minded chat component for mobile users. To spark ideas, I looked at how mainstream discreet-messaging tools polish their UI—everything from bubble gradients to “clear chat” warnings. The most concise roundup I found was this guide to modern sexting messengers: top sexting apps—scrolling through it gives instant inspiration on layout conventions, safety prompts, and monetization hooks you might want to borrow for any chat or DM feature.

If your project also needs to present location-based premium services or profile listings, it helps to study how directories lay out cards, filters, and calls-to-action. I grabbed a few visual cues from Slixa Boulder’s escort listing—the way they surface key info above the fold and keep the card grid responsive is a practical model you can adapt for any marketplace UI.

The admin-only stylesheet trick

I had a heavier table look for admins. I loaded a second CSS file only on admin pages.

In resources/views/layouts/admin.blade.php:

@vite(['resources/css/app.css', 'resources/css/admin.css', 'resources/js/app.js'])

And the file:

resources/css/admin.css

.table {
  width: 100%;
  border-collapse: collapse;
}

.table th, .table td {
  padding: .75rem;
  border-bottom: 1px solid #e5e7eb;
}

.table tr:hover {
  background: #f9fafb;
}

Fast and clear. Users never saw it on public pages.

Little snags I hit (and fixed)

  • CSS didn’t change? I forgot npm run dev. I ran it, and it worked.
  • Wrong import path. I wrote @import 'custom.css' instead of ./custom.css. Fixed the dot, fixed the day.
  • Tailwind fights. I moved my overrides to the bottom. That did it.
  • Cache made me grumpy. A hard refresh helped