npm

Getting started

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.

terminal
npm install @viliha/vui-theme
npm install -D tailwindcss @tailwindcss/vite
vite.config.js
import { 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(),
  ],
});
resources/css/app.css
@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.

resources/views/layouts/app.blade.php
<!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

Laravel already knows the locale, so setting 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

resources/views/components/vui/button.blade.php
@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>
using it
<x-vui.button variant="primary">Save</x-vui.button>

Badge

resources/views/components/vui/badge.blade.php
@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>
using it
<x-vui.badge variant="success">Active</x-vui.badge>

Card

resources/views/components/vui/card.blade.php
@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>
using it
<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?

  • Alpine ships with Laravel and covers dropdowns, dialogs and tabs: x-data, x-show, x-trap, @click.outside. Style the result with the classes from the HTML page.
  • Livewire works the same way. VUI is CSS, so a component that re-renders server-side keeps its styling with no extra work.
  • Inertia with React or Vue is the other path, and the most complete one: install @viliha/vui-ui (React) or @viliha/vui-vue and you get the real components, datatable included, inside your Laravel app.

What we do and do not ship

There is no Blade component package on Packagist. What is published is the theme, and the recipes on this page. If you want the components rather than the markup, Inertia plus the React package is the shortest path, and it is the one we would take.