LaraGrid Theming — Six Color Schemes, Dark Mode & CSS Tokens
Every grid below is the same definition — identical columns, identical rows,
identical footer. Only ->theme() or ->density() differs.
Flip the ◐ Theme switch in the header to see all of them in dark mode
at once: dark is nothing but token flipping under a .dark ancestor, so
every scheme ships a coordinated dark variant for free.
Every visual is a --lgrid-* CSS custom property with a self-contained
default, so the grid looks right on a page with no CSS framework at all — this demo has
no Tailwind build. In a Tailwind v4 app it adopts your --color-*
@theme palette automatically. All elements carry stable
lgrid-* semantic classes, so any part is restylable and nothing is ever
purged by a build tool. Print collapses to a clean black-on-white table — try
Ctrl+P.
The six shipped schemes
Grid::make('items')->theme('blue') — an unknown name fails loudly at
build time rather than rendering an unstyled grid. Set
'theme' => 'emerald' in config/laragrid.php to change the
app-wide default; any grid's own ->theme() still wins.
->theme('zinc') — Neutral zinc — the default register look
->theme('blue') — Blue — the classic line-of-business accent
->theme('emerald') — Emerald — entry screens and confirmations
->theme('amber') — Amber — warnings, ageing, exceptions
->theme('rose') — Rose — variance and error-led reports
->theme('violet') — Violet — vouchers and journals
A custom scheme in two properties
Internally each preset is just an accent pair; every surface derives from it through a
shared color-mix formula. So your own brand scheme is a class with two
custom properties, handed to ->themeClass():
/* your stylesheet */
.lgrid--theme-brand {
--lgrid-theme-accent: #0f766e;
--lgrid-theme-accent-dark: #2dd4bf;
}
// your component
Grid::make('items')->themeClass('lgrid--theme-brand')
->themeClass('lgrid--theme-brand') — teal, defined entirely in this page's CSS
Row density
Three presets trade vertical room for rows-on-screen:
GridDensity::Compact for registers an operator scans all day,
Normal for entry screens, Comfortable for touch and
presentation. The app-wide default lives at laragrid.density.
->density(GridDensity::Compact)
->density(GridDensity::Normal)
->density(GridDensity::Comfortable)
Overriding individual tokens
A scheme sets the accent; every other token is still yours. Override them globally,
under your own ->themeClass(), or under .dark:
.lgrid {
--lgrid-row-h: 1.75rem; /* row height */
--lgrid-cell-pad-x: .5rem; /* horizontal padding */
--lgrid-font-size: .8125rem; /* cell type size */
--lgrid-border: #e4e4e7; /* every grid line */
--lgrid-header-bg: #f4f4f5; /* header surface */
--lgrid-footer-bg: #f4f4f5; /* footer surface */
--lgrid-stripe-bg: #fafafa; /* striped rows */
--lgrid-cell-bg: #fff; /* cell surface */
--lgrid-text: #27272a; /* cell text */
--lgrid-accent: #2563eb; /* ring, selection */
--lgrid-error: #f43f5e; /* invalid cells */
--lgrid-dirty: #fbbf24; /* unsaved-cell corner */
}
The source
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\View\View;
use LaraGrid\Aggregate;
use LaraGrid\Columns\ComputedColumn;
use LaraGrid\Columns\DecimalColumn;
use LaraGrid\Columns\IntegerColumn;
use LaraGrid\Columns\SerialColumn;
use LaraGrid\Columns\TextColumn;
use LaraGrid\Grid;
use LaraGrid\GridDensity;
use LaraGrid\Support\CellHtml;
/**
* The THEMING showcase — one identical grid rendered under every shipped color scheme, a
* custom two-token scheme, and all three row densities.
*
* How theming works: every visual is a `--lgrid-*` CSS custom property with a self-contained
* default, so the grid looks right on a page with no CSS framework at all (this demo has no
* Tailwind build). Each shipped scheme is really just an ACCENT PAIR — light and dark — and
* every other surface (header, footer, stripes, borders, selection tint, focus ring) derives
* from it through a shared color-mix formula. That is why adding your own scheme is two
* custom properties on a class you hand to ->themeClass(), and why dark mode is nothing but
* token flipping under a `.dark` ancestor.
*/
class ThemesController extends Controller
{
/** The presets that ship with the package; ->theme() validates against exactly this list. */
private const THEMES = [
'zinc' => 'Neutral zinc — the default register look',
'blue' => 'Blue — the classic line-of-business accent',
'emerald' => 'Emerald — entry screens and confirmations',
'amber' => 'Amber — warnings, ageing, exceptions',
'rose' => 'Rose — variance and error-led reports',
'violet' => 'Violet — vouchers and journals',
];
private const DENSITIES = [
'compact' => GridDensity::Compact,
'normal' => GridDensity::Normal,
'comfortable' => GridDensity::Comfortable,
];
/** @var list<array<string, mixed>> */
private const ROWS = [
['code' => 'RM-101', 'item' => 'Deluxe Room — Garden View', 'qty' => 12, 'rate' => '4500.00', 'amount' => '54000.00', 'state' => 'ok'],
['code' => 'RM-204', 'item' => 'Executive Suite', 'qty' => 4, 'rate' => '9800.00', 'amount' => '39200.00', 'state' => 'ok'],
['code' => 'FB-018', 'item' => 'Breakfast Buffet (per pax)', 'qty' => 32, 'rate' => '650.00', 'amount' => '20800.00', 'state' => 'watch'],
['code' => 'SP-007', 'item' => 'Spa — Aromatherapy 60 min', 'qty' => 6, 'rate' => '2750.00', 'amount' => '16500.00', 'state' => 'ok'],
['code' => 'TR-055', 'item' => 'Airport Transfer — Sedan', 'qty' => 9, 'rate' => '1900.00', 'amount' => '17100.00', 'state' => 'hold'],
];
public function index(): View
{
return view('themes.index', [
'themes' => self::THEMES,
'themeGrids' => array_map(
fn (string $theme): Grid => $this->sample('theme-'.$theme)->theme($theme),
array_combine(array_keys(self::THEMES), array_keys(self::THEMES)),
),
// A scheme the package has never heard of: two custom properties on a class.
'brandGrid' => $this->sample('theme-brand')->themeClass('lgrid--theme-brand'),
'densityGrids' => array_map(
fn (GridDensity $density): Grid => $this->sample('density-'.$density->value)
->theme('blue')->density($density),
self::DENSITIES,
),
'rows' => self::ROWS,
]);
}
/**
* One definition, reused for every swatch — so the only difference between the grids
* below really is the theme or the density.
*/
private function sample(string $name): Grid
{
return Grid::make($name)
->toolbar(false)
->columns([
SerialColumn::make(),
TextColumn::make('code')->label('Code')->width(90),
TextColumn::make('item')->label('Item')->sortable()->minWidth(220)->grow(),
IntegerColumn::make('qty')->label('Qty')->sortable()->width(70)->align('right'),
DecimalColumn::make('rate')->label('Rate')->scale(2)->sortable()
->width(110)->align('right')->format('inr', ['scale' => 2]),
DecimalColumn::make('amount')->label('Amount')->scale(2)->sortable()
->width(120)->align('right')->format('inr', ['scale' => 2]),
ComputedColumn::make('status')->label('Status')->html()->width(90)->align('center')
->state(fn (array $row): string => match ($row['state'] ?? 'ok') {
'watch' => CellHtml::badge('amber', 'Watch'),
'hold' => CellHtml::badge('red', 'On hold'),
default => CellHtml::badge('green', 'Ready'),
}),
])
->footer([
Aggregate::sum('qty')->format('number'),
Aggregate::sum('amount')->format('inr', ['scale' => 2]),
])
->stickyHeader()
->striped()
->maxHeight('none');
}
}
<x-layouts.app>
<h1>LaraGrid Theming — Six Color Schemes, Dark Mode & CSS Tokens</h1>
<p class="lede">
Every grid below is the <em>same definition</em> — identical columns, identical rows,
identical footer. Only <code>->theme()</code> or <code>->density()</code> differs.
Flip the <strong>◐ Theme</strong> switch in the header to see all of them in dark mode
at once: dark is nothing but token flipping under a <code>.dark</code> ancestor, so
every scheme ships a coordinated dark variant for free.
</p>
<p class="keys">
Every visual is a <code>--lgrid-*</code> CSS custom property with a self-contained
default, so the grid looks right on a page with no CSS framework at all — this demo has
no Tailwind build. In a Tailwind v4 app it adopts your <code>--color-*</code>
<code>@theme</code> palette automatically. All elements carry stable
<code>lgrid-*</code> semantic classes, so any part is restylable and nothing is ever
purged by a build tool. Print collapses to a clean black-on-white table — try
<kbd>Ctrl</kbd>+<kbd>P</kbd>.
</p>
<h2>The six shipped schemes</h2>
<p class="lede">
<code>Grid::make('items')->theme('blue')</code> — an unknown name fails loudly at
build time rather than rendering an unstyled grid. Set
<code>'theme' => 'emerald'</code> in <code>config/laragrid.php</code> to change the
app-wide default; any grid's own <code>->theme()</code> still wins.
</p>
@foreach ($themes as $name => $blurb)
<section class="swatch">
<h3><code>->theme('{{ $name }}')</code> <span class="muted">— {{ $blurb }}</span></h3>
<x-laragrid :grid="$themeGrids[$name]" :rows="$rows" />
</section>
@endforeach
<h2>A custom scheme in two properties</h2>
<p class="lede">
Internally each preset is just an accent pair; every surface derives from it through a
shared <code>color-mix</code> formula. So your own brand scheme is a class with two
custom properties, handed to <code>->themeClass()</code>:
</p>
<pre class="snippet"><code>/* your stylesheet */
.lgrid--theme-brand {
--lgrid-theme-accent: #0f766e;
--lgrid-theme-accent-dark: #2dd4bf;
}</code></pre>
<pre class="snippet"><code>// your component
Grid::make('items')->themeClass('lgrid--theme-brand')</code></pre>
<section class="swatch">
<h3><code>->themeClass('lgrid--theme-brand')</code> <span class="muted">— teal, defined entirely in this page's CSS</span></h3>
<x-laragrid :grid="$brandGrid" :rows="$rows" />
</section>
<h2>Row density</h2>
<p class="lede">
Three presets trade vertical room for rows-on-screen:
<code>GridDensity::Compact</code> for registers an operator scans all day,
<code>Normal</code> for entry screens, <code>Comfortable</code> for touch and
presentation. The app-wide default lives at <code>laragrid.density</code>.
</p>
@foreach (['compact' => 'Compact', 'normal' => 'Normal', 'comfortable' => 'Comfortable'] as $key => $label)
<section class="swatch">
<h3><code>->density(GridDensity::{{ $label }})</code></h3>
<x-laragrid :grid="$densityGrids[$key]" :rows="$rows" />
</section>
@endforeach
<h2>Overriding individual tokens</h2>
<p class="lede">
A scheme sets the accent; every other token is still yours. Override them globally,
under your own <code>->themeClass()</code>, or under <code>.dark</code>:
</p>
<pre class="snippet"><code>.lgrid {
--lgrid-row-h: 1.75rem; /* row height */
--lgrid-cell-pad-x: .5rem; /* horizontal padding */
--lgrid-font-size: .8125rem; /* cell type size */
--lgrid-border: #e4e4e7; /* every grid line */
--lgrid-header-bg: #f4f4f5; /* header surface */
--lgrid-footer-bg: #f4f4f5; /* footer surface */
--lgrid-stripe-bg: #fafafa; /* striped rows */
--lgrid-cell-bg: #fff; /* cell surface */
--lgrid-text: #27272a; /* cell text */
--lgrid-accent: #2563eb; /* ring, selection */
--lgrid-error: #f43f5e; /* invalid cells */
--lgrid-dirty: #fbbf24; /* unsaved-cell corner */
}</code></pre>
<h2 id="source">The source</h2>
<x-source-code title="Theming source" :files="[
'app/Http/Controllers/ThemesController.php',
'resources/views/themes/index.blade.php',
]" />
@push('styles')
<style>
/* The custom scheme demonstrated above — the whole of it. */
.lgrid--theme-brand {
--lgrid-theme-accent: #0f766e;
--lgrid-theme-accent-dark: #2dd4bf;
}
.swatch { margin: 0 0 1.75rem; }
.swatch h3 { margin: 0 0 .4rem; font-weight: 600; }
.snippet { margin: 0 0 1rem; padding: .8rem 1rem; border: 1px solid #e4e4e7; border-radius: .5rem; background: #f4f4f5; color: #18181b; overflow-x: auto; }
html.dark .snippet { border-color: #27272a; background: #18181b; color: #e4e4e7; }
.snippet code { background: none; border: 0; padding: 0; font-size: .78rem; line-height: 1.6; color: inherit; }
</style>
@endpush
</x-layouts.app>