Kompass is a modern content management system (CMS) built on the Laravel framework, utilizing the TALL stack (Tailwind CSS V4, Alpine.js, Laravel, and Livewire V4). It is designed to provide a seamless and dynamic user experience for content management. The project is a Laravel package that can be installed into any Laravel application.
Key Technologies:
- Backend: PHP 8.2+, Laravel 11+
- Frontend: Tailwind CSS V4, Alpine.js, Vite, daisyui v5
- Dynamic UI: Livewire V4
- Database: Not specified, but likely MySQL, PostgreSQL, or SQLite (standard for Laravel)
- Image Processing: Intervention Image ∏
-
Require the package:
composer require secondnetwork/kompass
-
Run the installation command: This command will install frontend assets, create a new admin user, and drop all tables from the database.
php artisan kompass:install
-
Start the Vite development server:
bun run dev
-
**Build for production:
bun run build
To keep the frontend assets up-to-date, add the following to your composer.json file:
"scripts": {
"post-update-cmd": [
"@php artisan vendor:publish --tag=kompass.assets --force"
]
}- Styling: The project uses Tailwind CSS for styling. Configuration can be found in
tailwind.config.js. - Frontend Build: Vite is used for frontend asset bundling. The configuration is in
vite.config.js. - Dynamic Components: Livewire is used for creating dynamic interfaces. Livewire components are located in
src/Livewire. - Blade Components: The project utilizes Blade components, which are registered in the
KompassServiceProvider. - Image Handling: Intervention Image is used for image processing, with configuration in
config/kompass.php. - Coding Style: While not explicitly defined, the code follows standard Laravel and PSR conventions.
- Package Development: This project is a Laravel package, so development involves working within the
srcdirectory and testing its integration with a Laravel application.
composer.json: Defines PHP dependencies and project metadata.package.json: Defines JavaScript dependencies and build scripts.vite.config.js: Configuration for the Vite frontend build tool.config/kompass.php: Main configuration file for the Kompass package.src/KompassServiceProvider.php: The main service provider that registers all the package's resources.routes/web.php: Defines the routes for the CMS.resources/views: Contains the Blade templates for the CMS.resources/css: Contains the CSS for the CMS.resources/js: Contains the JavaScript for the CMS.README.md: Provides an overview of the project and installation instructions.
Source: https://livewire.laravel.com/docs/4.x/contribution-guide
This snippet shows how to fork, clone, and set up the Livewire project locally for development. It includes installing composer dependencies and configuring Dusk. This process requires the GitHub CLI.
# Fork and clone Livewire
gh repo fork livewire/livewire --default-branch-only --clone=true --remote=false -- livewire
# Switch the working directory to livewire
cd livewire
# Install all composer dependencies
composer install
# Ensure Dusk is correctly configured
vendor/bin/dusk-updater detect --no-interactionSource: https://livewire.laravel.com/docs/4.x/contribution-guide
This snippet outlines the steps to fork, clone, and build Alpine.js locally, including installing NPM dependencies and linking packages. This is necessary for contributing to Livewire when Alpine.js integration is involved. Requires NPM.
# Fork and clone Alpine
gh repo fork alpinejs/alpine --default-branch-only --clone=true --remote=false -- alpine
# Switch the working directory to alpine
cd alpine
# Install all npm dependencies
npm install
# Build all Alpine packages
npm run build
# Link all Alpine packages locally
cd packages/alpinejs && npm link && cd ../../
cd packages/anchor && npm link && cd ../../
cd packages/collapse && npm link && cd ../../
cd packages/csp && npm link && cd ../../
cd packages/docs && npm link && cd ../../
cd packages/focus && npm link && cd ../../
cd packages/history && npm link && cd ../../
cd packages/intersect && npm link && cd ../../
cd packages/mask && npm link && cd ../../
cd packages/morph && npm link && cd ../../
cd packages/navigate && npm link && cd ../../
cd packages/persist && npm link && cd ../../
cd packages/sort && npm link && cd ../../
cd packages/ui && npm link && cd ../../
# Switch the working directory back to livewire
cd ../livewire
# Link all packages
npm link alpinejs @alpinejs/anchor @alpinejs/collapse @alpinejs/csp @alpinejs/docs @alpinejs/focus @alpinejs/history @alpinejs/intersect @alpinejs/mask @alpinejs/morph @alpinejs/navigate @alpinejs/persist @alpinejs/sort @alpinejs/ui
# Build Livewire
npm run buildSource: https://livewire.laravel.com/docs/4.x/quickstart
Starts the Laravel development server. This command is used to run the application locally for testing and development purposes. Access the application via the provided URL.
php artisan serveSource: https://livewire.laravel.com/docs/4.x/installation
An example of a default Livewire layout file (app.blade.php) including essential HTML structure, asset loading using Vite, and Livewire specific directives (@livewireStyles, @livewireScripts).
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ $title ?? config('app.name') }}</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
@livewireStyles
</head>
<body>
{{ $slot }}
@livewireScripts
</body>
</html>Source: https://livewire.laravel.com/docs/4.x/csp
Example of how to enable Livewire's CSP-safe mode by setting the 'csp_safe' option to true in the config/livewire.php file.
'csp_safe' => true,Source: https://livewire.laravel.com/docs/4.x/javascript
Example of implementing custom loading indicators for specific components using message interceptors.
## Message Interceptor: Loading States
### Description
Adds a loading class to a component's element when a message is sent and removes it when finished.
### Method
JavaScript Function
### Endpoint
N/A
### Parameters
#### Callback Parameters
- **component** (object) - The component instance.
- **onSend** (function) - Callback executed when the request is sent.
- **onFinish** (function) - Callback executed when message processing is complete.
### Request Example
```javascript
Livewire.interceptMessage(({ component, onSend, onFinish }) => {
onSend(() => {
component.el.classList.add('is-loading');
});
onFinish(() => {
component.el.classList.remove('is-loading');
});
});
N/A
N/A
--------------------------------
### Setup Methods
Source: https://livewire.laravel.com/docs/4.x/testing
Methods for configuring the testing environment before interacting with components.
```APIDOC
## Setup Methods
### `Livewire::test('component.name')`
**Description:** Test the specified Livewire component.
**Method:** `test`
**Endpoint:** N/A
### `Livewire::test(ComponentClass::class, ['param' => $value])`
**Description:** Test a Livewire component using its class, optionally passing parameters to the `mount()` method.
**Method:** `test`
**Endpoint:** N/A
### `Livewire::actingAs($user)`
**Description:** Set the authenticated user for the current test.
**Method:** `actingAs`
**Endpoint:** N/A
### `Livewire::withQueryParams(['key' => 'value'])`
**Description:** Set URL query parameters for the test request.
**Method:** `withQueryParams`
**Endpoint:** N/A
### `Livewire::withCookie('name', 'value')`
**Description:** Set a single cookie for the test request.
**Method:** `withCookie`
**Endpoint:** N/A
### `Livewire::withCookies(['key1' => 'value1', 'key2' => 'value2'])`
**Description:** Set multiple cookies for the test request.
**Method:** `withCookies`
**Endpoint:** N/A
### `Livewire::withHeaders(['X-Header' => 'value'])`
**Description:** Set custom HTTP headers for the test request.
**Method:** `withHeaders`
**Endpoint:** N/A
### `Livewire::withoutLazyLoading()`
**Description:** Disable lazy loading for all components within this test.
**Method:** `withoutLazyLoading`
**Endpoint:** N/A
Source: https://livewire.laravel.com/docs/4.x/csp
Provides an example of Content Security Policy (CSP) headers configured to work with Livewire's CSP-safe build. It emphasizes removing 'unsafe-eval' and using nonce-based script loading.
Content-Security-Policy: default-src 'self';
script-src 'nonce-[random]' 'strict-dynamic';
style-src 'self' 'unsafe-inline';Source: https://livewire.laravel.com/docs/4.x/volt
This command installs the Livewire Volt package into your project using Composer. This is the initial step to enable Volt's functional API for creating Livewire components.
composer require livewire/voltSource: https://livewire.laravel.com/docs/4.x/testing
Installs the necessary dependencies for browser testing with Livewire and Pest. This involves using Composer for the Pest plugin and npm for Playwright, followed by Playwright's browser binary installation.
composer require pestphp/pest-plugin-browser --dev
npm install playwright@latest
npx playwright installSource: https://livewire.laravel.com/docs/4.x/wire-click
Defines a Livewire component with a public property and a method to handle a file download. It demonstrates how to integrate with Eloquent models and return a downloadable response.
<?php
namespace App\Livewire;
use Livewire\Component;
use App\Models\Invoice;
class ShowInvoice extends Component
{
public Invoice $invoice;
public function download()
{
return response()->download(
$this->invoice->file_path, 'invoice.pdf'
);
}
}Source: https://livewire.laravel.com/docs/4.x/contribution-guide
This snippet demonstrates the basic structure of a unit test in PHP for Livewire. It extends a TestCase class and includes a placeholder for test logic. Unit tests focus on the PHP implementation.
use Tests\TestCase;
class UnitTest extends TestCase
{
public function test_livewire_can_run_action(): void
{
// ...
}
}Source: https://livewire.laravel.com/docs/4.x/nesting
A simple Livewire component that can be rendered dynamically. This serves as a placeholder for 'step-one' content in a multi-step form example. It demonstrates the basic structure of a Livewire component.
<?php // resources/views/components/⚡step-one.blade.php
use Livewire\Component;
new class extends Component {
//
};
?>
<div>
Step One Content
</div>Source: https://livewire.laravel.com/docs/4.x/navigate
Example of defining routes for Livewire components in Laravel's web.php file. This setup allows for easy integration of Livewire components as distinct pages within a Laravel application.
use App\Livewire\Dashboard;
use App\Livewire\ShowPosts;
use App\Livewire\ShowUsers;
Route::livewire('/', 'pages::dashboard');
Route::livewire('/posts', 'pages::show-posts');
Route::livewire('/users', 'pages::show-users');Source: https://livewire.laravel.com/docs/4.x/installation
Installs the Livewire v4 beta version into a Laravel application using Composer. Ensure you have Laravel 10+ and PHP 8.1+.
composer require livewire/livewire:^4.0@betaSource: https://livewire.laravel.com/docs/4.x/pagination
Demonstrates using Laravel's simplePaginate() method for faster and simpler pagination, showing only next and previous links. Requires a Post model and a 'show-posts' view.
public function render()
{
return view('show-posts', [
'posts' => Post::simplePaginate(10),
]);
}Source: https://livewire.laravel.com/docs/4.x/morphing
This PHP component demonstrates a simple 'Todos' example used to illustrate Livewire's morphing functionality. It includes properties for the current todo item and a list of existing todos, along with a method to add a new todo.
class Todos extends Component
{
public $todo = '';
public $todos = [
'first',
'second',
];
public function add()
{
$this->todos[] = $this->todo;
}
}Source: https://livewire.laravel.com/docs/4.x/components
Provides examples of using Artisan to create a component within a custom namespace and rendering that component in a Blade view using the namespace prefix.
php artisan make:livewire admin::users-table
<livewire:admin::users-table />Source: https://livewire.laravel.com/docs/4.x/installation
Import Livewire and Alpine.js, along with any desired Alpine.js plugins, in your resources/js/app.js file when manually bundling assets. This sets up Livewire and Alpine for use in your application.
import { Livewire, Alpine } from '../../vendor/livewire/livewire/dist/livewire.esm';
import Clipboard from '@ryangjchandler/alpine-clipboard'
Alpine.plugin(Clipboard)
Livewire.start()Source: https://livewire.laravel.com/docs/4.x/testing
This snippet shows the Composer commands to remove PHPUnit and install Pest as the development dependency, which is recommended for testing Livewire components in version 4.x.
composer remove phpunit/phpunit
composer require pestphp/pest --dev --with-all-dependenciesSource: https://livewire.laravel.com/docs/4.x/testing
This Artisan command generates a Livewire component and automatically creates an associated test file, simplifying the setup for testing. The example shows the generated test file for a view-based component.
php artisan make:livewire post.create --test<?php
use Livewire\Livewire;
it('renders successfully', function () {
Livewire::test('post.create')
->assertStatus(200);
});Source: https://livewire.laravel.com/docs/4.x/testing
Demonstrates how to test Livewire components using PHPUnit, showcasing equivalent functionality to the Pest examples. This includes creating posts and asserting validation rules.
<?php
namespace Tests\Feature\Livewire;
use Livewire\Livewire;
use App\Models\Post;
use Tests\TestCase;
class CreatePostTest extends TestCase
{
public function test_can_create_post()
{
$this->assertEquals(0, Post::count());
Livewire::test('post.create')
->set('title', 'My new post')
->set('content', 'Post content')
->call('save');
$this->assertEquals(1, Post::count());
}
public function test_title_is_required()
{
Livewire::test('post.create')
->set('title', '')
->call('save')
->assertHasErrors('title');
}
}Source: https://livewire.laravel.com/docs/4.x/installation
Manually bundle Livewire and Alpine.js using your JavaScript build tool (like Vite) for fine-grained control over initialization and to use Alpine.js plugins. Replace @livewireScripts with @livewireScriptConfig in your layout. This method requires importing Livewire and Alpine in your app.js file.
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ $title ?? config('app.name') }}</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
@livewireStyles
</head>
<body>
{{ $slot }}
@livewireScriptConfig
</body>
</html>Source: https://livewire.laravel.com/docs/4.x/contribution-guide
This snippet shows the fundamental structure for a browser test in PHP for Livewire. It inherits from BrowserTestCase and provides a method for defining browser interaction tests. Browser tests primarily target the JavaScript implementation.
use Tests\BrowserTestCase;
class BrowserTest extends BrowserTestCase
{
public function test_livewire_can_run_action()
{
// ...
}
}Source: https://livewire.laravel.com/docs/4.x/javascript
Example of implementing custom error handling for specific HTTP status codes like session expiration or forbidden access.
## Request Interceptor: Global Error Handling
### Description
Provides a centralized way to handle specific HTTP error status codes globally.
### Method
JavaScript Function
### Endpoint
N/A
### Parameters
#### Callback Parameters
- **onError** (function) - Callback executed on error status codes.
- **response** (object) - The HTTP response object.
- **preventDefault** (function) - Function to prevent Livewire's default error handling.
### Request Example
```javascript
Livewire.interceptRequest(({ onError }) => {
onError(({ response, preventDefault }) => {
if (response.status === 419) {
preventDefault();
if (confirm('Your session has expired. Refresh the page?')) {
window.location.reload();
}
}
if (response.status === 403) {
preventDefault();
alert('You do not have permission to perform this action');
}
});
});
N/A
N/A
--------------------------------
### Livewire Post Creation Component (PHP)
Source: https://livewire.laravel.com/docs/4.x/quickstart
Defines a Livewire component with properties for title and content, and a save method for validation and data handling. It includes a basic HTML form with Livewire directives for data binding and submission. The save method demonstrates validation and can be extended for database interaction.
```php
<?php
use Livewire\Component;
new class extends Component {
public string $title = '';
public string $content = '';
public function save()
{
$this->validate([
'title' => 'required|max:255',
'content' => 'required',
]);
dd($this->title, $this->content);
}
};
?>
<form wire:submit="save">
<label>
Title
<input type="text" wire:model="title">
@error('title') <span style="color: red;">{{ $message }}</span> @enderror
</label>
<label>
Content
<textarea wire:model="content" rows="5"></textarea>
@error('content') <span style="color: red;">{{ $message }}</span> @enderror
</label>
<button type="submit">Save Post</button>
</form>
Source: https://livewire.laravel.com/docs/4.x/attribute-url
Provides a practical Livewire component example demonstrating how to filter products using multiple URL query parameters. It showcases #[Url] attributes for search, category, price range, and sorting, synchronizing them with the URL and updating the product list dynamically.
<?php // resources/views/pages/⚡products.blade.php
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use App\Models\Product;
new class extends Component {
#[Url(as: 'q')]
public $search = '';
#[Url]
public $category = 'all';
#[Url]
public $minPrice = 0;
#[Url]
public $maxPrice = 1000;
#[Url]
public $sort = 'name';
#[Computed]
public function products()
{
return Product::query()
->when($this->search, fn($q) => $q->search($this->search))
->when($this->category !== 'all', fn($q) => $q->where('category', $this->category))
->whereBetween('price', [$this->minPrice, $this->maxPrice])
->orderBy($this->sort)
->paginate(20);
}
};
?>
<div>
<input type="text" wire:model.live="search" placeholder="Search products...">
<select wire:model.live="category">
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
</select>
<input type="range" wire:model.live="minPrice" min="0" max="1000">
<input type="range" wire:model.live="maxPrice" min="0" max="1000">
<select wire:model.live="sort">
<option value="name">Name</option>
<option value="price">Price</option>
<option value="created_at">Newest</option>
</select>
@foreach($this->products as $product)
<div wire:key="{{ $product->id }}">{{ $product->name }} - ${{ $product->price }}</div>
@endforeach
</div>Source: https://livewire.laravel.com/docs/4.x/quickstart
Registers a web route for the post creation page. When a user visits '/post/create', Laravel will render the specified Livewire component ('pages::post.create') within the application's layout.
Route::livewire('/post/create', 'pages::post.create');Source: https://livewire.laravel.com/docs/4.x/contribution-guide
This section details the Git commands necessary for preparing your code changes for a pull request. It covers creating a new branch, staging changes, committing them with a message, and pushing the branch to a remote repository.
git checkout -b my-feature
git add .
git commit -m "Add my feature"
git push origin my-feature
Enumerating objects: 13, done.
Counting objects: 100% (13/13), done.
Delta compression using up to 8 threads
Compressing objects: 100% (6/6), done.
To github.com:Username/livewire.git
* [new branch] my-feature -> my-featureSource: https://livewire.laravel.com/docs/4.x/downloads
Provides examples of how to test file downloads in Livewire using the assertFileDownloaded() and assertNoFileDownloaded() methods. These assertions help verify that the correct file is downloaded or that no download occurs.
use App\Models\Invoice;
public function test_can_download_invoice()
{
$invoice = Invoice::factory();
Livewire::test(ShowInvoice::class)
->call('download')
->assertFileDownloaded('invoice.pdf');
}use App\Models\Invoice;
public function test_does_not_download_invoice_if_unauthorised()
{
$invoice = Invoice::factory();
Livewire::test(ShowInvoice::class)
->call('download')
->assertNoFileDownloaded();
}Source: https://livewire.laravel.com/docs/4.x/morphing
This is the HTML output generated by the 'Todos' Livewire component during its initial render. It represents the starting state of the to-do list before any user interaction.
<form wire:submit="add">
<ul>
<li>first</li>
<li>second</li>
</ul>
<input wire:model="todo">
</form>Source: https://livewire.laravel.com/docs/4.x/loading-states
This code provides examples of how to style elements based on the data-loading attribute using standard CSS, without relying on Tailwind CSS variants. It demonstrates styling the element itself and its children.
[data-loading] {
opacity: 0.5;
}
button[data-loading] {
background-color: #ccc;
}
[data-loading] .loading-text {
display: inline;
}
[data-loading] .default-text {
display: none;
}Source: https://livewire.laravel.com/docs/4.x/javascript
A basic Livewire component in PHP demonstrating a counter with an increment method and a render method to display a view. This serves as a basis for understanding the $wire object's interaction with server-side logic.
<?php
namespace App\Livewire;
use Livewire\Component;
class Counter extends Component
{
public $count = 1;
public function increment()
{
$this->count++;
}
public function render()
{
return view('livewire.counter');
}
}Source: https://livewire.laravel.com/docs/4.x/installation
Publishes Livewire's configuration file to your Laravel application's config directory, allowing for customization of its settings.
php artisan livewire:publish --configSource: https://livewire.laravel.com/docs/4.x/quickstart
An enhanced Livewire component's save method that includes database interaction. After validation, it creates a new 'Post' record using Eloquent and redirects the user. This assumes a 'Post' model and corresponding database table are set up.
public function save()
{
$validated = $this->validate([
'title' => 'required|max:255',
'content' => 'required',
]);
Post::create($validated); // Assumes you have a Post model and database table
return $this->redirect('/posts');
}Source: https://livewire.laravel.com/docs/4.x/javascript
A practical example of using message interceptors to add and remove a CSS class for visual loading indicators on specific components. It attaches a 'is-loading' class on message send and removes it upon completion.
Livewire.interceptMessage(({ component, onSend, onFinish }) => {
onSend(() => {
component.el.classList.add('is-loading')
})
onFinish(() => {
component.el.classList.remove('is-loading')
})
})Source: https://livewire.laravel.com/docs/4.x/csp
Examples of basic Livewire expressions that are supported when CSP-safe mode is enabled. These include simple actions and model bindings.
<!-- These work -->
<button wire:click="increment">+</button>
<button wire:click="decrement">-</button>
<button wire:click="reset">Reset</button>
<button wire:click="save">Save</button>
<input wire:model="name">
<input wire:model.live="search">Source: https://livewire.laravel.com/docs/4.x/alpine
Provides an example of registering a custom Alpine.js directive, 'x-clipboard', within the main JavaScript file for use across a Livewire application.
import { Livewire, Alpine } from '../../vendor/livewire/livewire/dist/livewire.esm';
Alpine.directive('clipboard', (el) => {
let text = el.textContent
el.addEventListener('click', () => {
navigator.clipboard.writeText(text)
})
})
Livewire.start()Source: https://livewire.laravel.com/docs/4.x/synthesizers
This JSON illustrates the serialized state of a simple Livewire component with a plain string property. It's a straightforward key-value pair.
state: { title: '' }Source: https://livewire.laravel.com/docs/4.x/wire-loading
A reference guide to Livewire's wire:loading directive and its available modifiers. This includes directives for targeting specific actions or properties, and various modifiers for controlling display, class, attribute manipulation, and delay intervals.
wire:loading
wire:target="action"
wire:target="property"
wire:target.except="action"Source: https://livewire.laravel.com/docs/4.x/alpine
Details the setup required to manually bundle Alpine.js with your JavaScript build using Vite, by including the @livewireScriptConfig directive in the layout and importing Livewire and Alpine in app.js.
<html>
<head>
<!-- ... -->
@livewireStyles
@vite(['resources/js/app.js'])
</head>
<body>
{{ $slot }}
@livewireScriptConfig
</body>
</html>import { Livewire, Alpine } from '../../vendor/livewire/livewire/dist/livewire.esm';
// Register any Alpine directives, components, or plugins here...
Livewire.start()Source: https://livewire.laravel.com/docs/4.x/pagination
Illustrates using Laravel's cursorPaginate() for efficient pagination on large datasets. The URL will store an encoded cursor instead of a page number.
public function render()
{
return view('show-posts', [
'posts' => Post::cursorPaginate(10),
]);
}Source: https://livewire.laravel.com/docs/4.x/volt
After installing the Volt package, this Artisan command publishes Volt's service provider. This provider configures the directories where Volt will look for single-file components.
php artisan volt:installSource: https://livewire.laravel.com/docs/4.x/wire-navigate
This example demonstrates how to use the wire:navigate directive to enable Single Page Application-like navigation. When links with this directive are clicked, Livewire intercepts the navigation, fetches the content in the background, and swaps the current page, resulting in faster and smoother transitions.
<nav>
<a href="/" wire:navigate>Dashboard</a>
<a href="/posts" wire:navigate>Posts</a>
<a href="/users" wire:navigate>Users</a>
</nav>Source: https://livewire.laravel.com/docs/4.x/contribution-guide
These commands illustrate how to execute tests within the Livewire project using the PHPUnit test runner. You can run all tests or filter to execute a specific test case.
vendor/bin/phpunit --filter "test_can_make_method_a_computed" # To run a specific test
vendor/bin/phpunit # To run all testsSource: https://livewire.laravel.com/docs/4.x/synthesizers
This PHP code defines a Livewire Synthesizer for the Address Data Transfer Object (DTO). It enables wire:model binding to the Address object's properties by implementing match, dehydrate, hydrate, get, and set methods. The get and set methods are crucial for direct property manipulation during data binding.
use App\Dtos\Address;
class AddressSynth extends Synth
{
public static $key = 'address';
public static function match($target)
{
return $target instanceof Address;
}
public function dehydrate($target)
{
return [[
'street' => $target->street,
'city' => $target->city,
'state' => $target->state,
'zip' => $target->zip,
], []];
}
public function hydrate($value)
{
$instance = new Address;
$instance->street = $value['street'];
$instance->city = $value['city'];
$instance->state = $value['state'];
$instance->zip = $value['zip'];
return $instance;
}
public function get(&$target, $key)
{
return $target->{$key};
}
public function set(&$target, $key, $value)
{
$target->{$key} = $value;
}
}Source: https://livewire.laravel.com/docs/4.x/javascript
An example demonstrating global error handling for specific HTTP status codes using request interceptors. It customizes responses for session expiration (419) and forbidden access (403) by preventing default handling and showing user-friendly messages or prompts.
Livewire.interceptRequest(({ onError }) => {
onError(({ response, preventDefault }) => {
if (response.status === 419) {
// Session expired
preventDefault()
if (confirm('Your session has expired. Refresh the page?')) {
window.location.reload()
}
}
if (response.status === 403) {
// Forbidden
preventDefault()
alert('You do not have permission to perform this action')
}
})
})Source: https://livewire.laravel.com/docs/4.x/wire-navigate
This example shows how to prefetch pages when a user hovers over a link using the .hover modifier with wire:navigate. This optimization ensures that the target page is downloaded from the server before the user clicks, leading to near-instantaneous navigation upon click.
<a href="/" wire:navigate.hover>Dashboard</a>Source: https://livewire.laravel.com/docs/4.x/wire-sort
This example shows how to prevent specific elements within a sortable list item from initiating drag operations using the wire:sort:ignore directive. This is useful for interactive elements like buttons, allowing users to click them without starting a sort. The main list remains sortable.
<ul wire:sort="sortItem">
@foreach ($todo->items as $item)
<li wire:sort:item="{{ $item->id }}">
{{ $item->title }}
<div wire:sort:ignore>
<button type="button">Edit</button>
</div>
</li>
@endforeach
</ul>Source: https://livewire.laravel.com/docs/4.x/attribute-async
This PHP snippet illustrates a dangerous anti-pattern in Livewire: using the #[Async] attribute for an action that mutates component state. This can lead to unpredictable race conditions and lost updates, as shown with the 'increment' counter example.
// Warning: This snippet demonstrates what NOT to do...
<?php // resources/views/components/⚡counter.blade.php
use Livewire\Attributes\Async;
use Livewire\Component;
new class extends Component {
public $count = 0;
#[Async] // Don't do this!
public function increment()
{
$this->count++; // State mutation in an async action
}
};Source: https://livewire.laravel.com/docs/4.x/installation
Generates a default layout file for Livewire components, typically located at resources/views/layouts/app.blade.php. This file includes necessary Livewire directives.
php artisan livewire:layoutSource: https://livewire.laravel.com/docs/4.x/downloads
Demonstrates how to use Laravel's Storage facade within a Livewire component to initiate a file download. This approach is useful when files are stored using Laravel's filesystem abstraction.
public function download()
{
return Storage::disk('invoices')->download('invoice.csv');
}Source: https://livewire.laravel.com/docs/4.x/volt
This example illustrates how to implement pagination in Livewire Volt functional components using the usesPagination function. It shows how to fetch paginated data using Post::paginate(10) and render pagination links. It also covers switching to Bootstrap styling.
<?php
use function Livewire\Volt\{with, usesPagination};
usesPagination();
with(fn () => ['posts' => Post::paginate(10)]);
?>
<div>
@foreach ($posts as $post)
//
@endforeach
{{ $posts->links() }}
</div>usesPagination(theme: 'bootstrap');Source: https://livewire.laravel.com/docs/4.x/csp
Shows examples of accessing and updating nested properties (e.g., user.name) and using $set with properties, which are supported in CSP-safe mode.
<!-- These work -->
<input wire:model="user.name">
<input wire:model="settings.theme">
<button wire:click="$set('user.active', true)">Activate</button>
<div wire:show="user.role === 'admin'">Admin Panel</div>Source: https://livewire.laravel.com/docs/4.x/installation
This HTML snippet demonstrates how to include Livewire's assets using @livewireStyles and @livewireScripts. This is necessary to enable Alpine.js functionality on pages that do not contain Livewire components. The @livewireScripts directive should be placed before the closing </body> tag.
<!DOCTYPE html>
<html>
<head>
@livewireStyles
</head>
<body>
<!-- No Livewire components, but we want Alpine -->
<div x-data="{ open: false }">
<button @click="open = !open">Toggle</button>
</div>
@livewireScripts
</body>
</html>Source: https://livewire.laravel.com/docs/4.x/components
Example of a basic single-file Livewire component. It includes a PHP class extending Livewire\Component with a public property and a save method, and a Blade template with input and button elements bound via wire directives.
<?php
use Livewire\Component;
new class extends Component {
public $title = '';
public function save()
{
// Save logic here...
}
};
?>
<div>
<input wire:model="title" type="text">
<button wire:click="save">Save Post</button>
</div>Source: https://livewire.laravel.com/docs/4.x/uploads
This code example shows how to customize the global validation rules for temporary file uploads in Livewire. By modifying the rules key within the temporary_file_upload configuration array in config/livewire.php, you can specify allowed file types (mimes) and maximum file sizes. The example sets a 100MB limit and restricts uploads to PNG, JPEG, and PDF formats.
'temporary_file_upload' => [
// ...
'rules' => 'file|mimes:png,jpg,pdf|max:102400', // (100MB max, and only accept PNGs, JPEGs, and PDFs)
],Source: https://livewire.laravel.com/docs/4.x/actions
This example illustrates using Laravel's dependency injection within Livewire actions. By type-hinting a parameter with a repository or service, Livewire and Laravel automatically resolve and inject the dependency from the container.
<?php // resources/views/components/post/⚡index.blade.php
use Illuminate\Support\Facades\Auth;
use App\Repositories\PostRepository;
use Livewire\Attributes\Computed;
use Livewire\Component;
new class extends Component {
#[Computed]
public function posts()
{
return Auth::user()->posts;
}
public function delete(PostRepository $posts, $postId)
{
$posts->deletePost($postId);
}
};
<div>
@foreach ($this->posts as $post)
<div wire:key="{{ $post->id }}">
<h1>{{ $post->title }}</h1>
<span>{{ $post->content }}</span>
<button wire:click="delete({{ $post->id }})">Delete</button>
</div>
@endforeach
</div>Source: https://livewire.laravel.com/docs/4.x/uploads
This PHP configuration example shows how to change the default directory where Livewire temporarily stores uploaded files. By updating the directory option within the temporary_file_upload configuration in config/livewire.php, you can specify a custom path on the configured disk. The example sets the directory to 'tmp'.
'temporary_file_upload' => [
// ...
'directory' => 'tmp',
],Source: https://livewire.laravel.com/docs/4.x/nesting
Provides examples of lifecycle methods within a Livewire nested component. These methods, such as mount() and updated(), allow the child component to manage its own initialization, authorization, and response to property updates independently of the parent component.
public function mount($todo)
{
$this->authorize('view', $todo);
}
public function updated($property)
{
// Child-specific update logic
}Source: https://livewire.laravel.com/docs/4.x/directive-teleport
Provides examples of using the @teleport directive for common UI patterns like modal dialogs, dropdown menus, and toast notifications. These examples illustrate targeting specific elements like 'body' or custom containers for rendering the teleported content, ensuring proper display regardless of parent element styling.
@teleport('body')
<div class="fixed inset-0 bg-black/50" x-show="showModal">
<div class="modal">
<!-- Modal content... -->
</div>
</div>
@endteleport@teleport('body')
<div class="absolute" x-show="open" style="top: {{ $top }}px; left: {{ $left }}px;">
<!-- Dropdown items... -->
</div>
@endteleport@teleport('#notifications-container')
<div class="toast">
{{ $message }}
</div>
@endteleportSource: https://livewire.laravel.com/docs/4.x/properties
Demonstrates how to set initial values for component properties using the mount() method in Livewire. This is useful for populating data when a component first renders.
<?php // resources/views/components/⚡todos.blade.php
use LivewireComponent;
new class extends Component {
public $todos = [];
public $todo = '';
public function mount()
{
$this->todos = ['Buy groceries', 'Walk the dog', 'Write code'];
}
// ...
};Source: https://livewire.laravel.com/docs/4.x/directive-teleport
Illustrates the constraint for the @teleport directive requiring a single root element within its statement. The 'Valid' example shows a single div wrapping the content, while the 'Invalid' example demonstrates the error of using multiple root elements directly inside @teleport.
@teleport('body')
<div>
<h2>Title</h2>
<p>Content</p>
</div>
@endteleport@teleport('body')
<h2>Title</h2>
<p>Content</p>
@endteleportSource: https://livewire.laravel.com/docs/4.x/wire-click
Shows how to use the wire:click directive to invoke a component's method when an HTML element is clicked. Includes examples for basic calls and passing parameters.
<button type="button" wire:click="download">
Download Invoice
</button>
<button wire:click="delete({{ $post->id }})">Delete</button>Source: https://livewire.laravel.com/docs/4.x/attribute-reactive
Illustrates a scenario where Livewire props are not reactive by default. When a parent component updates, only the parent's state is sent to the server, not the child's. This example shows that without #[Reactive], child components won't automatically update when parent data changes.
<?php // resources/views/components/⚡todos.blade.php
use Livewire\Component;
new class extends Component {
public $todos = [];
public function addTodo($text)
{
$this->todos[] = ['text' => $text];
// Child components with $todos props won't automatically update
}
};
?>
<div>
<livewire:todo-count :$todos />
<button wire:click="addTodo('New task')">Add Todo</button>
</div>Source: https://livewire.laravel.com/docs/4.x/testing
This command initializes the Pest testing framework in your Laravel project, typically creating a tests/Pest.php configuration file.
./vendor/bin/pest --initSource: https://livewire.laravel.com/docs/4.x/testing
Demonstrates testing communication between Livewire components through event dispatches. This example shows how a 'post-created' event affects a 'post-count-badge' component.
it('updates post count when event is dispatched', function () {
$badge = Livewire::test('post-count-badge')
->assertSee('0');
Livewire::test('post.create')
->set('title', 'New post')
->call('save')
->assertDispatched('post-created');
$badge->dispatch('post-created')
->assertSee('1');
});Source: https://livewire.laravel.com/docs/4.x/downloads
This example demonstrates how to trigger a file download from a Livewire component using a standard Laravel download response. It includes the component's PHP class and its corresponding Blade view, showing how to attach the download action to a button.
<?php // resources/views/components/⚡show-invoice.blade.php
use Livewire\Component;
use App\Models\Invoice;
new class extends Component {
public Invoice $invoice;
public function mount(Invoice $invoice)
{
$this->invoice = $invoice;
}
public function download()
{
return response()->download(
$this->invoice->file_path, 'invoice.pdf'
);
}
};<div>
<h1>{{ $invoice->title }}</h1>
<span>{{ $invoice->date }}</span>
<span>{{ $invoice->amount }}</span>
<button type="button" wire:click="download">Download</button>
</div>Source: https://livewire.laravel.com/docs/4.x/hydration
Illustrates the JSON snapshot created by Livewire during the dehydration process. This snapshot captures the component's state and memo information, essential for re-creating the component on subsequent server requests.
{
"state": {
"count": 1
},
"memo": {
"name": "counter",
"id": "1526456"
}
}
Source: https://livewire.laravel.com/docs/4.x/installation
Add a post-update command to your composer.json file to automatically publish Livewire assets when Livewire is updated. This ensures your published assets remain current with the Livewire version.
{
"scripts": {
"post-update-cmd": [
"@php artisan vendor:publish --tag=livewire:assets --ansi --force"
]
}
}
Source: https://livewire.laravel.com/docs/4.x/javascript
Register callbacks for when Livewire is initialized on the page. 'livewire:init' runs before initialization, and 'livewire:initialized' runs immediately after. These are useful for setting up custom extensions or configurations before Livewire fully loads.
document.addEventListener('livewire:init', () => {
// Runs after Livewire is loaded but before it's initialized
// on the page...
})
document.addEventListener('livewire:initialized', () => {
// Runs immediately after Livewire has finished initializing
// on the page...
})Source: https://livewire.laravel.com/docs/4.x/wire-model
This example demonstrates binding a select dropdown to a Livewire property, where the options are generated dynamically using a Blade foreach loop iterating over a collection of states.
<select wire:model="state">
@foreach (\App\Models\State::all() as $state)
<option value="{{ $state->id }}">{{ $state->label }}</option>
@endforeach
</select>Source: https://livewire.laravel.com/docs/4.x/testing
Verify that specific events are dispatched from a Livewire component using assertDispatched(). This example checks if a 'post-created' event is dispatched.
it('dispatches event when post is created', function () {
Livewire::test('post.create')
->set('title', 'New post')
->call('save')
->assertDispatched('post-created');
});Source: https://livewire.laravel.com/docs/4.x/uploads
This Livewire component, written in PHP, handles file uploads using the WithFileUploads trait. It accepts a file upload and stores it to a specified disk and path. This component is designed to work with the file upload testing example.
<?php // resources/views/components/⚡upload-photo.blade.php
use Livewire\WithFileUploads;
use Livewire\Component;
new class extends Component {
use WithFileUploads;
public $photo;
public function upload($name)
{
$this->photo->storeAs('/', $name, disk: 'avatars');
}
// ...
};Source: https://livewire.laravel.com/docs/4.x/testing
Test if events are dispatched with specific parameters using assertDispatched(). This example checks for a 'notify' event with a 'message' parameter.
it('dispatches notification when deleting post', function () {
Livewire::test('post.show')
->call('delete', postId: 3)
->assertDispatched('notify', message: 'Post deleted');
});Source: https://livewire.laravel.com/docs/4.x/installation
Publish Livewire's JavaScript assets to your public directory using the php artisan livewire:publish --assets command. This allows serving assets directly via your web server. Ensure assets are updated after Livewire updates by adding a script to composer.json.
php artisan livewire:publish --assets
Source: https://livewire.laravel.com/docs/4.x/components
Demonstrates how to register individual class-based components, locations for components, and namespaces for components within a service provider.
use Livewire\Livewire;
// In a service provider's boot() method (e.g., App\Providers\AppServiceProvider)
// Register an individual class-based component
Livewire::addComponent(
name: 'todos',
\App\Livewire\Todos::class
);
// Register a location for class-based components
Livewire::addLocation(
classNamespace: 'App\Admin\Livewire'
);
// Create a namespace for class-based components
Livewire::addNamespace(
namespace: 'admin',
classNamespace: 'App\Admin\Livewire',
classPath: app_path('Admin/Livewire'),
classViewPath: resource_path('views/admin/livewire')
);Source: https://livewire.laravel.com/docs/4.x/security
A Livewire component demonstrating property validation using #[Validate] and an update method. This component fetches and updates a 'Post' model, relying on route-level middleware for initial authorization.
<?php
use App\Models\Post;
use Livewire\Component;
use Livewire\Attributes\Validate;
class UpdatePost extends Component
{
public Post $post;
#[Validate('required|min:5')]
public $title = '';
public $content = '';
public function mount()
{
$this->title = $this->post->title;
$this->content = $this->post->content;
}
public function update()
{
$this->post->update([
'title' => $this->title,
'content' => $this->content,
]);
}
}Source: https://livewire.laravel.com/docs/4.x/security
Shows how to customize the default Livewire update route to apply middleware globally to all Livewire AJAX/fetch requests. This example applies 'LocalizeViewPaths' middleware.
Livewire::setUpdateRoute(function ($handle) {
return Route::post('/livewire/update', $handle)
->middleware(App\Http\Middleware\LocalizeViewPaths::class);
});Source: https://livewire.laravel.com/docs/4.x/upgrading
Commands to create Livewire components in single-file or multi-file formats. The --mfc flag enables multi-file components, and livewire:convert can switch between formats.
php artisan make:livewire create-post # Single-file (default)
php artisan make:livewire create-post --mfc # Multi-file
php artisan livewire:convert create-post # Convert between formatsSource: https://livewire.laravel.com/docs/4.x/attribute-layout
Shows a pattern for utilizing different layouts for various sections of an application. This example demonstrates assigning distinct layouts ('admin', 'marketing', 'dashboard') to different full-page components, promoting a modular and organized structure.
// Admin pages
new #[Layout('layouts::admin')] class extends Component { }
// Marketing pages
new #[Layout('layouts::marketing')] class extends Component { }
// Dashboard pages
new #[Layout('layouts::dashboard')] class extends Component { }Source: https://livewire.laravel.com/docs/4.x/synthesizers
This JSON represents the serialized state of a Livewire component when a property is a Laravel Stringable. It uses a metadata tuple to indicate the original type, allowing Livewire to hydrate it correctly.
state: { title: ['', { s: 'str' }] }Tailwind CSS is a utility-first CSS framework that generates styles by scanning HTML, JavaScript, and template files for class names. It provides a comprehensive design system through CSS utility classes, enabling rapid UI development without writing custom CSS. The framework operates at build-time, analyzing source files and generating only the CSS classes actually used in the project, resulting in optimized production bundles with zero runtime overhead.
The framework includes an extensive default color palette (18 colors with 11 shades each), responsive breakpoint system, customizable design tokens via CSS custom properties, and support for dark mode, pseudo-classes, pseudo-elements, and media queries through variant prefixes. Tailwind CSS v4.1 introduces CSS-first configuration using the @theme directive, native support for custom utilities via @utility, seamless integration with modern build tools through Vite, PostCSS, and framework-specific plugins, and enhanced arbitrary value syntax for maximum flexibility.
Installing Tailwind CSS using the Vite plugin for modern JavaScript frameworks.
# Create a new Vite project
npm create vite@latest my-project
cd my-project
# Install Tailwind CSS and Vite plugin
npm install tailwindcss @tailwindcss/vite// vite.config.ts
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
tailwindcss(),
],
})/* src/style.css */
@import "tailwindcss";<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/src/style.css" rel="stylesheet">
</head>
<body>
<h1 class="text-3xl font-bold underline">
Hello world!
</h1>
</body>
</html>Applying conditional styles using variant prefixes for hover, focus, and responsive breakpoints.
<!-- Hover and focus states -->
<button class="bg-sky-500 hover:bg-sky-700 focus:outline-2 focus:outline-offset-2 focus:outline-sky-500 active:bg-sky-800">
Save changes
</button>
<!-- Responsive breakpoints -->
<div class="grid grid-cols-3 md:grid-cols-4 lg:grid-cols-6">
<!-- 3 columns on mobile, 4 on tablets, 6 on desktop -->
</div>
<!-- Dark mode support -->
<div class="bg-white dark:bg-gray-800 text-gray-900 dark:text-white">
Content adapts to color scheme preference
</div>
<!-- Multiple variants stacked -->
<button class="bg-violet-500 hover:bg-violet-600 focus:ring-2 focus:ring-violet-300 disabled:opacity-50 disabled:cursor-not-allowed md:text-lg">
Submit
</button>Defining custom design tokens using the @theme directive in CSS.
/* app.css */
@import "tailwindcss";
@theme {
/* Custom fonts */
--font-display: "Satoshi", "sans-serif";
--font-body: "Inter", system-ui, sans-serif;
/* Custom colors */
--color-brand-50: oklch(0.98 0.02 264);
--color-brand-100: oklch(0.95 0.05 264);
--color-brand-500: oklch(0.55 0.22 264);
--color-brand-900: oklch(0.25 0.12 264);
/* Custom breakpoints */
--breakpoint-3xl: 120rem;
--breakpoint-4xl: 160rem;
/* Custom spacing */
--spacing-18: calc(var(--spacing) * 18);
/* Custom animations */
--ease-fluid: cubic-bezier(0.3, 0, 0, 1);
--ease-snappy: cubic-bezier(0.2, 0, 0, 1);
}<!-- Using custom theme tokens -->
<div class="font-display text-brand-500 3xl:text-6xl">
Custom design system
</div>Using square bracket notation for one-off custom values without leaving HTML.
<!-- Arbitrary property values -->
<div class="top-[117px] lg:top-[344px]">
Pixel-perfect positioning
</div>
<div class="bg-[#bada55] text-[22px] before:content-['Festivus']">
Custom hex colors, font sizes, and content
</div>
<!-- Arbitrary properties -->
<div class="[mask-type:luminance] hover:[mask-type:alpha]">
Any CSS property
</div>
<!-- CSS variables -->
<div class="bg-(--my-brand-color) fill-(--icon-color)">
Reference custom properties
</div>
<!-- Grid with arbitrary values -->
<div class="grid grid-cols-[1fr_500px_2fr]">
Complex grid layouts
</div>
<!-- Type hints for ambiguous values -->
<div class="text-(length:--my-var)">
Font size from CSS variable
</div>
<div class="text-(color:--my-var)">
Color from CSS variable
</div>Working with Tailwind's comprehensive color palette and opacity modifiers.
<!-- Using default color palette -->
<div class="bg-sky-500 border-pink-300 text-gray-950">
Color utilities across all properties
</div>
<!-- Opacity modifiers -->
<div class="bg-black/75 text-white/90">
Alpha channel with percentage
</div>
<div class="bg-pink-500/[71.37%]">
Arbitrary opacity values
</div>
<div class="bg-cyan-400/(--my-alpha-value)">
Opacity from CSS variable
</div>
<!-- Dark mode color variants -->
<div class="bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-200 dark:border-gray-700">
<span class="text-pink-600 dark:text-pink-400">
Adapts to color scheme
</span>
</div>
<!-- Color utilities reference -->
<!-- bg-* (background), text-* (text), border-* (border) -->
<!-- decoration-* (text decoration), outline-* (outline) -->
<!-- shadow-* (box shadow), ring-* (ring shadow) -->
<!-- accent-* (form controls), caret-* (text cursor) -->
<!-- fill-* (SVG fill), stroke-* (SVG stroke) -->Implementing dark mode with CSS media queries or manual toggle.
<!-- Using prefers-color-scheme (default) -->
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
<div class="bg-gray-100 dark:bg-gray-800 p-6 rounded-lg">
Content automatically adapts
</div>
</div>/* Manual dark mode toggle with class selector */
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));<!-- Manual dark mode -->
<html class="dark">
<body>
<div class="bg-white dark:bg-black">
Controlled by .dark class
</div>
</body>
</html>// Dark mode toggle logic
// On page load or theme change
document.documentElement.classList.toggle(
"dark",
localStorage.theme === "dark" ||
(!("theme" in localStorage) && window.matchMedia("(prefers-color-scheme: dark)").matches)
);
// User chooses light mode
localStorage.theme = "light";
// User chooses dark mode
localStorage.theme = "dark";
// User chooses system preference
localStorage.removeItem("theme");Styling elements based on pseudo-classes and parent/sibling state.
<!-- Form state variants -->
<input
type="email"
required
class="border-gray-300
focus:border-sky-500
focus:ring-2
focus:ring-sky-300
invalid:border-pink-500
invalid:text-pink-600
disabled:bg-gray-100
disabled:opacity-50
placeholder:text-gray-400"
placeholder="[email protected]"
/>
<!-- List item variants -->
<ul role="list">
<li class="py-4 first:pt-0 last:pb-0 odd:bg-gray-50 even:bg-white">
Item content
</li>
</ul>
<!-- Parent state with group -->
<a href="#" class="group">
<h3 class="text-gray-900 group-hover:text-white">Title</h3>
<p class="text-gray-500 group-hover:text-white">Description</p>
</a>
<!-- Sibling state with peer -->
<form>
<input type="email" class="peer" />
<p class="invisible peer-invalid:visible text-red-500">
Please provide a valid email address.
</p>
</form>
<!-- Has variant -->
<label class="has-checked:bg-indigo-50 has-checked:ring-indigo-200">
<input type="radio" class="checked:border-indigo-500" />
Option
</label>Building mobile-first responsive layouts with breakpoint variants.
<!-- Mobile-first responsive grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6">
<!-- Adapts from 1 to 6 columns -->
</div>
<!-- Responsive spacing and typography -->
<div class="px-4 sm:px-6 lg:px-8">
<h1 class="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold">
Responsive heading
</h1>
<p class="mt-2 sm:mt-4 text-sm sm:text-base lg:text-lg">
Text scales with viewport
</p>
</div>
<!-- Container queries -->
<div class="@container">
<div class="flex flex-col @md:flex-row @lg:gap-8">
<!-- Responds to parent container width -->
</div>
</div>
<!-- Min/max width breakpoints -->
<div class="hidden md:block">Desktop only</div>
<div class="block md:hidden">Mobile only</div>
<div class="min-[900px]:grid-cols-3">Custom breakpoint</div>
<div class="max-md:text-center">Below medium</div>Creating reusable custom utility classes with variant support.
/* Simple custom utility */
@utility content-auto {
content-visibility: auto;
}
/* Complex utility with nesting */
@utility scrollbar-hidden {
&::-webkit-scrollbar {
display: none;
}
}
/* Functional utility with theme values */
@theme {
--tab-size-2: 2;
--tab-size-4: 4;
--tab-size-github: 8;
}
@utility tab-* {
tab-size: --value(--tab-size-*);
}
/* Supporting arbitrary, bare, and theme values */
@utility opacity-* {
opacity: --value([percentage]);
opacity: calc(--value(integer) * 1%);
opacity: --value(--opacity-*);
}
/* Utility with modifiers */
@utility text-* {
font-size: --value(--text-*, [length]);
line-height: --modifier(--leading-*, [length], [*]);
}
/* Negative value support */
@utility inset-* {
inset: --spacing(--value(integer));
inset: --value([percentage], [length]);
}
@utility -inset-* {
inset: --spacing(--value(integer) * -1);
inset: calc(--value([percentage], [length]) * -1);
}<!-- Using custom utilities -->
<div class="content-auto scrollbar-hidden tab-4">
Custom utilities work with variants
</div>
<div class="hover:tab-github lg:tab-[12]">
Variants and arbitrary values supported
</div>
<div class="text-2xl/relaxed">
Utility with modifier (font-size/line-height)
</div>Registering custom conditional styles with the @custom-variant directive.
/* Simple custom variant */
@custom-variant theme-midnight (&:where([data-theme="midnight"] *));
/* Variant with media query */
@custom-variant any-hover {
@media (any-hover: hover) {
&:hover {
@slot;
}
}
}
/* ARIA state variant */
@custom-variant aria-asc (&[aria-sort="ascending"]);
@custom-variant aria-desc (&[aria-sort="descending"]);
/* Data attribute variant */
@custom-variant data-checked (&[data-ui~="checked"]);<!-- Using custom variants -->
<html data-theme="midnight">
<button class="theme-midnight:bg-black theme-midnight:text-white">
Midnight theme button
</button>
</html>
<th aria-sort="ascending" class="aria-asc:rotate-0 aria-desc:rotate-180">
Sortable column
</th>
<div data-ui="checked active" class="data-checked:underline">
Checked state
</div>
<!-- Arbitrary variants -->
<div class="[&.is-dragging]:cursor-grabbing [&_p]:mt-4">
One-off custom selectors
</div>Using the @variant directive to apply variants within custom CSS.
/* Single variant */
.my-element {
background: white;
@variant dark {
background: black;
}
}
/* Nested variants */
.my-button {
background: white;
@variant dark {
background: gray;
@variant hover {
background: black;
}
}
}
/* Compiled output */
.my-element {
background: white;
}
@media (prefers-color-scheme: dark) {
.my-element {
background: black;
}
}Organizing custom styles into Tailwind's cascade layers.
@import "tailwindcss";
/* Base styles for HTML elements */
@layer base {
h1 {
font-size: var(--text-2xl);
font-weight: bold;
}
h2 {
font-size: var(--text-xl);
font-weight: 600;
}
body {
font-family: var(--font-body);
}
}
/* Reusable component classes */
@layer components {
.btn {
padding: --spacing(2) --spacing(4);
border-radius: var(--radius);
font-weight: 600;
transition: all 150ms;
}
.btn-primary {
background-color: var(--color-blue-500);
color: white;
}
.card {
background-color: var(--color-white);
border-radius: var(--radius-lg);
padding: --spacing(6);
box-shadow: var(--shadow-xl);
}
/* Third-party component overrides */
.select2-dropdown {
border-radius: var(--radius);
box-shadow: var(--shadow-lg);
}
}<!-- Components can be overridden by utilities -->
<div class="card rounded-none">
Square corners despite card class
</div>
<button class="btn btn-primary hover:bg-blue-600 disabled:opacity-50">
Component with utility overrides
</button>Using Tailwind's CSS functions for dynamic values and opacity adjustments.
/* Alpha function for opacity */
.my-element {
color: --alpha(var(--color-lime-300) / 50%);
background: --alpha(var(--color-blue-500) / 25%);
}
/* Spacing function */
.my-element {
margin: --spacing(4);
padding: calc(--spacing(6) - 1px);
}
/* In arbitrary values */
<div class="py-[calc(--spacing(4)-1px)] mt-[--spacing(8)]">
<!-- ... -->
</div>
/* Source directive for additional content */
@source "../node_modules/@my-company/ui-lib";
/* Apply directive for inline utilities */
.select2-dropdown {
@apply rounded-b-lg shadow-md;
}
.select2-search {
@apply rounded border border-gray-300;
}
.select2-results__group {
@apply text-lg font-bold text-gray-900;
}Styling ::before, ::after, ::placeholder, and other pseudo-elements.
<!-- Required field indicator -->
<label>
<span class="after:ml-0.5 after:text-red-500 after:content-['*']">
Email
</span>
<input type="email" class="placeholder:text-gray-400 placeholder:italic" placeholder="[email protected]" />
</label>
<!-- File input styling -->
<input
type="file"
class="file:mr-4 file:rounded-full file:border-0 file:bg-violet-50 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-violet-700 hover:file:bg-violet-100"
/>
<!-- Custom list markers -->
<ul class="list-disc marker:text-sky-400">
<li>First item</li>
<li>Second item</li>
</ul>
<!-- Text selection styling -->
<div class="selection:bg-fuchsia-300 selection:text-fuchsia-900">
<p>Select this text to see custom colors</p>
</div>
<!-- First letter drop cap -->
<p class="first-letter:float-left first-letter:mr-3 first-letter:text-7xl first-letter:font-bold first-line:uppercase first-line:tracking-widest">
Typography with pseudo-elements
</p>Conditional styling based on user preferences and device capabilities.
<!-- Reduced motion -->
<button class="transition hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
Respects user preference
</button>
<button class="motion-safe:animate-spin">
Only animates if motion allowed
</button>
<!-- Contrast preference -->
<label>
<input class="contrast-more:border-gray-400 contrast-less:border-gray-100" />
<p class="opacity-75 contrast-more:opacity-100">
Adjusts for contrast needs
</p>
</label>
<!-- Pointer type -->
<div class="grid grid-cols-4 gap-2 pointer-coarse:grid-cols-2 pointer-coarse:gap-4">
<!-- Larger touch targets on touch devices -->
</div>
<!-- Orientation -->
<div class="portrait:hidden">
Hidden in portrait mode
</div>
<div class="landscape:grid-cols-2">
Layout adapts to orientation
</div>
<!-- Print styles -->
<article class="print:hidden">
Not shown when printing
</article>
<div class="hidden print:block">
Only visible in print
</div>
<!-- Feature support -->
<div class="flex supports-[display:grid]:grid supports-backdrop-filter:backdrop-blur">
Progressive enhancement
</div>Tailwind CSS provides a complete utility-first design system that eliminates the need for writing custom CSS in most cases. The framework's primary use cases include rapid prototyping, building production applications with consistent design systems, creating responsive layouts, implementing dark mode, and maintaining design consistency across large teams. By using utility classes directly in markup, developers can iterate quickly, avoid naming conventions, and prevent CSS bloat since only used styles are generated.
The v4.1 release enhances the developer experience with CSS-first configuration, eliminating JavaScript configuration files for most projects. Integration patterns include using the Vite plugin for modern frameworks, PostCSS for custom build pipelines, the Tailwind CLI for simple projects, and CDN scripts for rapid prototyping. The framework excels at component-driven development when combined with React, Vue, Svelte, or other modern frameworks, where utility classes are co-located with component logic. Custom design systems can be fully defined in CSS using @theme, with project-specific utilities and variants extending the framework's capabilities without writing JavaScript plugins.