Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Twigcn Bundle Laravel Package

ducrot/twigcn-bundle

TwigcnBundle brings shadcn/ui-inspired, accessible UI components to Symfony & Twig. Install the PHP bundle plus the @ducrot/twigcn-ui NPM package and compile Tailwind CSS to use ready-made, themeable components in your templates.

View on GitHub
Deep Wiki
Context7

TwigcnBundle

CI Packagist PHP License

Read-only mirror. This repository is a subtree split of ducrot/twigcn and exists so Composer/Packagist can ship the bundle. Issues and pull requests are intentionally disabled here — please open them in the main repository.

Beautiful, accessible UI components for Symfony & Twig. Inspired by shadcn/ui, built for Symfony.

Requirements

  • PHP 8.2+
  • Symfony 7.0+
  • Node.js 22+ (for asset compilation)
  • Tailwind CSS 4.0+

Installation

Step 1: Install the PHP Bundle

composer require ducrot/twigcn-bundle

The bundle registers automatically via Symfony Flex.

Step 2: Install the NPM Package

npm install @ducrot/twigcn-ui

If your project uses AssetMapper (the Symfony 7.x webapp default), also expose the package to the importmap so it can be imported from JavaScript:

php bin/console importmap:require @ducrot/twigcn-ui

Webpack Encore projects pick up node_modules automatically and do not need this step.

Step 3: Configure Your CSS

Tailwind processes a source file into a static stylesheet. Keep the source out of assets/ (so AssetMapper does not also serve it raw) — for example tailwind.css at the project root:

@import "tailwindcss";
@import "@ducrot/twigcn-ui/styles";

/* IMPORTANT: Scan bundle templates for Tailwind classes */
@source "../vendor/ducrot/twigcn-bundle/templates";

/* Scan your own templates */
@source "./templates/**/*.html.twig";

/* Optional: Override theme variables */
:root {
    --primary: #6b5fc3;
    --primary-foreground: #ffffff;
    --radius: 0.5rem;
}

Then build with the Tailwind CLI (Symfony does not bundle CSS itself):

npx @tailwindcss/cli -i tailwind.css -o assets/styles/app.css --watch

The compiled output at assets/styles/app.css is what AssetMapper or Encore serves. Re-run the build whenever templates or styles change (or use --watch during development).

Step 4: Register Stimulus Controllers

Option A: Automatic (Symfony UX)

Create or update assets/controllers.json:

{
    "controllers": {
        "@ducrot/twigcn-ui": {
            "accordion": { "enabled": true },
            "carousel": { "enabled": true },
            "combobox": { "enabled": true },
            "command": { "enabled": true },
            "custom-select": { "enabled": true },
            "dialog": { "enabled": true },
            "drawer": { "enabled": true },
            "drawer-trigger": { "enabled": true },
            "popover": { "enabled": true },
            "slider": { "enabled": true },
            "tabs": { "enabled": true },
            "theme": { "enabled": true },
            "toaster": { "enabled": true },
            "tooltip": { "enabled": true }
        }
    }
}

Option B: Manual Registration

In a Symfony project that uses StimulusBundle (the default in the webapp recipe), register the controllers on the existing Stimulus application from assets/bootstrap.js (or assets/stimulus_bootstrap.js) — do not start a second one:

import { startStimulusApp } from '@symfony/stimulus-bundle';
import { registerControllers } from '@ducrot/twigcn-ui';

const app = startStimulusApp();
registerControllers(app);

For non-Symfony or fully custom Stimulus setups (e.g. Webpack Encore without StimulusBundle):

import { Application } from '@hotwired/stimulus';
import { registerControllers } from '@ducrot/twigcn-ui';

const app = Application.start();
registerControllers(app);

Alternative: Vite via pentatrion/vite-bundle

For projects that prefer a real bundler (recommended for Tailwind + TypeScript

  • Stimulus), use pentatrion/vite-bundle together with vite-plugin-symfony and @tailwindcss/vite. This is the setup the demo app in this repository uses. Vite replaces both the Tailwind CLI build (Tailwind 4 runs as a Vite plugin) and the StimulusBundle controller registration (vite-plugin-symfony provides its own Stimulus integration).

Install (replaces Steps 2–4 above):

composer require pentatrion/vite-bundle ducrot/twigcn-bundle
npm install @ducrot/twigcn-ui
npm install --save-dev vite vite-plugin-symfony @tailwindcss/vite tailwindcss

vite.config.ts:

import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
import symfonyPlugin from 'vite-plugin-symfony';
import { resolve } from 'path';

export default defineConfig({
    plugins: [
        symfonyPlugin({ stimulus: true }),
        tailwindcss(),
    ],
    publicDir: false,
    build: {
        manifest: true,
        outDir: 'public/build',
        rollupOptions: {
            input: { app: resolve(__dirname, 'assets/app.ts') },
        },
    },
});

assets/app.ts:

import './app.css';

import { Application } from '@hotwired/stimulus';
import { registerControllers } from '@ducrot/twigcn-ui';

const app = Application.start();
registerControllers(app);

assets/app.css:

@import "tailwindcss";
@import "@ducrot/twigcn-ui/styles";

/* Scan bundle templates *and* PHP component classes for Tailwind classes —
 * variant strings live in PHP, so the @source must include `src` too. */
@source "../vendor/ducrot/twigcn-bundle/templates";
@source "../vendor/ducrot/twigcn-bundle/src";
@source "../templates";

In your base Twig template:

{% block stylesheets %}
    {{ vite_entry_link_tags('app') }}
{% endblock %}

{% block javascripts %}
    {{ vite_entry_script_tags('app') }}
{% endblock %}

Then npm run dev for HMR or npm run build for production. The bundle's config/packages/pentatrion_vite.yaml in the demo shows the minimal Symfony-side configuration.

Usage

Components are namespaced under Twigcn: in Twig (Symfony UX TwigComponent prefixes third-party bundle components with their bundle namespace):

{# Button #}
<twig:Twigcn:Button variant="primary" size="lg">Click me</twig:Twigcn:Button>

{# Button as link #}
<twig:Twigcn:Button as="a" href="/dashboard" variant="outline">
    Go to Dashboard
</twig:Twigcn:Button>

{# Dialog (uses the native HTML <dialog> element) #}
<twig:Twigcn:Button onclick="document.getElementById('confirm-dialog').showModal()">
    Open Dialog
</twig:Twigcn:Button>

<twig:Twigcn:Dialog id="confirm-dialog">
    <header>
        <h2 class="text-lg font-semibold">Confirm Action</h2>
        <p class="text-muted-foreground">Are you sure you want to proceed?</p>
    </header>
    <footer>
        <twig:Twigcn:Button variant="outline" onclick="document.getElementById('confirm-dialog').close()">
            Cancel
        </twig:Twigcn:Button>
        <twig:Twigcn:Button variant="destructive" onclick="document.getElementById('confirm-dialog').close()">
            Confirm
        </twig:Twigcn:Button>
    </footer>
</twig:Twigcn:Dialog>

{# Drawer (open via the dedicated DrawerTrigger) #}
<twig:Twigcn:DrawerTrigger for="settings-drawer" class="btn-outline">
    Open Settings
</twig:Twigcn:DrawerTrigger>

<twig:Twigcn:Drawer id="settings-drawer" side="right">
    <twig:Twigcn:DrawerContent>
        <twig:Twigcn:DrawerHeader>
            <h3 class="text-lg font-semibold">Settings</h3>
        </twig:Twigcn:DrawerHeader>
        <div class="p-4">Drawer content goes here.</div>
        <twig:Twigcn:DrawerFooter>
            <twig:Twigcn:DrawerClose class="btn-outline">Close</twig:Twigcn:DrawerClose>
        </twig:Twigcn:DrawerFooter>
    </twig:Twigcn:DrawerContent>
</twig:Twigcn:Drawer>

{# Tabs #}
<twig:Twigcn:Tabs defaultValue="account">
    <twig:Twigcn:TabsList>
        <twig:Twigcn:TabsTrigger value="account">Account</twig:Twigcn:TabsTrigger>
        <twig:Twigcn:TabsTrigger value="password">Password</twig:Twigcn:TabsTrigger>
    </twig:Twigcn:TabsList>
    <twig:Twigcn:TabsContent value="account">
        <p>Manage your account settings here.</p>
    </twig:Twigcn:TabsContent>
    <twig:Twigcn:TabsContent value="password">
        <p>Change your password here.</p>
    </twig:Twigcn:TabsContent>
</twig:Twigcn:Tabs>

{# Accordion #}
<twig:Twigcn:Accordion type="single" collapsible>
    <twig:Twigcn:AccordionItem value="item-1" title="Is it accessible?">
        Yes. It adheres to the WAI-ARIA design pattern.
    </twig:Twigcn:AccordionItem>
    <twig:Twigcn:AccordionItem value="item-2" title="Is it styled?">
        Yes. It comes with default styles using Tailwind CSS.
    </twig:Twigcn:AccordionItem>
</twig:Twigcn:Accordion>

{# Form elements #}
<twig:Twigcn:Field>
    <twig:Twigcn:Label for="email">Email</twig:Twigcn:Label>
    <twig:Twigcn:Input type="email" id="email" placeholder="you@example.com" />
</twig:Twigcn:Field>

{# Alerts #}
<twig:Twigcn:Alert variant="destructive">
    <strong>Error!</strong> Something went wrong.
</twig:Twigcn:Alert>

Available Components

Form

  • Button - Clickable button with variants
  • ButtonGroup - Group of related buttons
  • Checkbox - Checkbox input
  • ChoiceCard - Selectable card-style option
  • Combobox - Autocomplete input with suggestions
  • CustomSelect - Enhanced select with search
  • Field - Form field wrapper
  • Form - Form container
  • Input - Text input field
  • InputGroup - Input with prefix/suffix slots
  • Label - Form label
  • Radio / RadioGroup - Radio buttons
  • Select - Native select dropdown
  • Slider - Range slider
  • Switch - Toggle switch
  • Textarea - Multi-line text input

Layout

  • Accordion / AccordionItem - Collapsible sections
  • Breadcrumb / BreadcrumbItem / BreadcrumbSeparator - Breadcrumb navigation
  • Card - Container with border and shadow
  • Pagination / PaginationItem - Page navigation
  • Sidebar - Collapsible sidebar navigation (with SidebarHeader, SidebarContent, SidebarFooter, SidebarGroup, SidebarMenu, SidebarMenuItem, SidebarMenuButton)
  • Table - Data table
  • Tabs / TabsList / TabsTrigger / TabsContent - Tabbed content panels

Overlay

  • Command - Command palette (with CommandInput, CommandList, CommandGroup, CommandItem, CommandShortcut, CommandSeparator, CommandEmpty)
  • Dialog - Modal dialog (use :closeOnBackdrop="false" for alert-dialog behavior)
  • Drawer - Slide-out panel (with DrawerTrigger, DrawerContent, DrawerHeader, DrawerFooter, DrawerClose)
  • DropdownMenu / DropdownMenuTrigger / DropdownMenuContent / DropdownMenuItem - Dropdown menu
  • Popover / PopoverTrigger / PopoverContent - Floating content on trigger
  • Tooltip - Hover tooltip

Feedback

  • Alert - Alert messages
  • Avatar - User avatar
  • Badge - Status badges
  • Carousel - Image/content carousel (with CarouselContent, CarouselItem, CarouselNext, CarouselPrevious)
  • Empty - Empty-state placeholder
  • Item - Generic content item
  • Kbd - Keyboard key display
  • Progress - Progress bar
  • Skeleton - Loading placeholder
  • Spinner - Loading spinner
  • ThemeSwitcher - Dark/light mode toggle
  • Toast / Toaster (with ToastTitle, ToastDescription, ToastClose) - Toast notifications

Naming Notes

A few PHP class names differ from their Twig component tag because the tag name is reserved or already taken in PHP:

Twig tag PHP class
<twig:Twigcn:Empty> EmptyState
<twig:Twigcn:Switch> SwitchComponent
<twig:Twigcn:Field> FieldForm

Theming

Components use CSS custom properties for theming. Override these in your CSS:

:root {
    /* Colors */
    --background: #ffffff;
    --foreground: #333333;
    --primary: #6b5fc3;
    --primary-foreground: #ffffff;
    --secondary: #e7e7ea;
    --secondary-foreground: #4e4d58;
    --muted: #ececef;
    --muted-foreground: #6c6b75;
    --accent: #d6d9f0;
    --accent-foreground: #433669;
    --destructive: #ef4444;
    --destructive-foreground: #ffffff;
    --border: #dbdadf;
    --input: #dbdadf;
    --ring: #6b5fc3;

    /* Radius */
    --radius: 0.375rem;
}

/* Dark mode */
.dark {
    --background: #171717;
    --foreground: #e5e5e5;
    /* ... other dark mode overrides */
}

Dark Mode

Toggle dark mode by adding/removing the dark class on the <html> element:

<twig:Twigcn:ThemeSwitcher />

Or manually:

document.documentElement.classList.toggle('dark');

Contributing & Issues

Development happens in ducrot/twigcn. Open issues at https://github.com/ducrot/twigcn/issues and pull requests against the same repo. Release notes are tracked in the monorepo's CHANGELOG.

License

MIT License

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
babenkoivan/elastic-client
innmind/static-analysis
innmind/coding-standard
datacore/hub-sdk
alengo/sulu-http-cache-bundle
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
imbo/imbo-coding-standard
visualbuilder/filament-lottie
servicioslineaonce/starter-kit
atomcoder/laravel-reorderable
irajul/filament-shadcn-theme
agtp/agtp-php
agtp/mod-php
centraldesktop/protobuf-php