Laravel and Blade
Laravel already ships Vite, Tailwind and Alpine, which is everything VUI needs. Import the theme, then write Blade components from the same class strings the React and Vue packages render, so a Blade page and a React page look like one product.
How do I add VUI to a Laravel app?
Two commands and one import. This assumes a Laravel 11 or 12 app with Vite, which is the default.
npm install @viliha/vui-theme
npm install -D tailwindcss @tailwindcss/viteimport { defineConfig } from "vite";
import laravel from "laravel-vite-plugin";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [
laravel({ input: ["resources/css/app.css", "resources/js/app.js"], refresh: true }),
tailwindcss(),
],
});@import "tailwindcss";
@import "@viliha/vui-theme/theme.css";
/* Tailwind needs to see your Blade files to emit the utilities they use. */
@source "../views/**/*.blade.php";That last line matters more in Laravel than anywhere else: Tailwind scans source files for class names, and Blade templates are not JavaScript, so they have to be named explicitly.
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}"
dir="{{ in_array(app()->getLocale(), ['ar', 'he', 'fa', 'ur']) ? 'rtl' : 'ltr' }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-background text-foreground">
{{ $slot }}
</body>
</html>Right to left comes free here
dirfrom it is one expression. VUI's own shell uses logical properties, so the layout flips without any extra CSS.Blade components
Blade's anonymous components are the right shape for this: a file per component, @props for the variants, and $attributes->merge() so a caller can still pass a class. Below is each one written out. The class strings are the same ones the React and Vue components render.
Button
@props(['variant' => 'default', 'size' => 'default'])
@php
$base = 'inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0';
$variants = [
'default' => 'border border-border bg-background text-foreground [&_svg]:text-muted-foreground hover:[&_svg]:text-foreground',
'primary' => 'border border-transparent bg-[var(--button-primary)] text-[var(--button-primary-foreground)] shadow-[var(--button-shadow)] hover:bg-[var(--button-primary-hover)] [&_svg]:border-[var(--button-primary-hover)]',
'ghost' => 'hover:bg-accent hover:text-accent-foreground',
'destructive' => 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
];
$sizes = [
'default' => 'h-8 px-3',
'sm' => 'h-7 rounded-sm px-2.5',
'lg' => 'h-9 px-4',
'icon' => 'size-8',
];
@endphp
<button {{ $attributes->merge(['type' => 'button', 'class' => $base.' '.$variants[$variant].' '.$sizes[$size]]) }}>
{{ $slot }}
</button><x-vui.button variant="primary">Save</x-vui.button>Badge
@props(['variant' => 'default'])
@php
$variants = [
'default' => 'border-transparent bg-primary text-primary-foreground',
'muted' => 'border-transparent bg-muted text-muted-foreground',
'success' => 'border-transparent bg-emerald-50 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-400',
'warning' => 'border-transparent bg-amber-50 text-amber-700 dark:bg-amber-950 dark:text-amber-400',
'destructive' => 'border-transparent bg-destructive/10 text-destructive dark:bg-destructive/20',
];
@endphp
<span {{ $attributes->merge(['class' => 'inline-flex items-center gap-1 rounded-md border px-2 py-0.5 font-normal '.$variants[$variant]]) }}>{{ $slot }}</span><x-vui.badge variant="success">Active</x-vui.badge>Card
@props(['title' => null])
<section {{ $attributes->merge(['class' => 'rounded-lg border border-border bg-card text-card-foreground overflow-hidden']) }}>
@if ($title)
<header class="border-b border-border bg-muted/40 px-5 py-3">
<h2 class="text-base font-semibold tracking-tight">{{ $title }}</h2>
</header>
@endif
<div class="px-5 py-4 text-sm leading-relaxed">{{ $slot }}</div>
</section><x-vui.card title="Team">Anything goes here.</x-vui.card>Everything else, as markup
Tables, menus, dialogs and form controls are plain markup you can paste straight into a Blade view. They are listed on the plain HTML page, and nothing about them is React-specific.
What about interactivity?
What we do and do not ship