Skip to content

nfacciolo/base-ui

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Reactic Base UI

A headless component library for Symfony and Twig, built on Base UI principles. This library provides unstyled, accessible UI components that give you complete control over styling while handling complex functionality, state management, and accessibility out of the box.

Perfect for building custom design systems without fighting against opinionated styles.

Features

  • Headless Components: Unstyled components with full control over appearance
  • Accessibility First: ARIA attributes and keyboard navigation built-in
  • Symfony Integration: Built with Symfony UX Twig Components
  • Stimulus Controllers: JavaScript interactivity powered by Stimulus
  • Flexible Styling: Use Tailwind, CSS, or any styling solution
  • Production Ready: Type-safe, tested, and optimized

Requirements

  • PHP 8.2 or higher
  • Symfony 7.4 or higher
  • Symfony UX Twig Component ^2.31
  • Symfony Stimulus Bundle ^2.0

Installation

1. Clone the bundle into your project

First, create a bundle directory at the root of your Symfony project and clone the repository:

# Create bundle directory if it doesn't exist
mkdir -p bundle

# Clone the repository into bundle/base-ui
cd bundle
git clone https://github.com/reactic/base-ui.git base-ui

# Or if you already have it, pull latest changes
cd base-ui
git pull origin main
cd ../..

2. Install the bundle via Composer

For local development (from bundle/ directory), add the repository to your composer.json:

// composer.json
{
    "repositories": [
        {
            "type": "path",
            "url": "bundle/base-ui"
        }
    ],
    "require": {
        "reactic/base-ui": "@dev"
    }
}

Then install the bundle:

composer require reactic/base-ui:@dev

3. Add assets dependency

Add the following line to your package.json in the dependencies section:

"@reactic/base-ui": "file:vendor/reactic/base-ui/assets"

Complete example:

{
  "dependencies": {
    "@reactic/base-ui": "file:vendor/reactic/base-ui/assets",
    "other-package": "^1.0.0"
  }
}

Then install the assets:

npm install

4. Build assets

Compile your assets using your build tool:

# For development with watch mode
npm run watch

# Or for a one-time build
npm run build

# Or for production
npm run build:prod

5. Enable the bundle

The bundle should be automatically enabled via Symfony Flex. If not, add it manually:

// config/bundles.php
return [
    // ...
    Reactic\BaseUi\BaseUiBundle::class => ['all' => true],
];

6. Configure Symfony UX dependencies

This bundle requires Symfony UX Twig Component and Symfony Stimulus Bundle to work properly.

If you haven't already set them up in your project, please refer to the official documentation:

7. Register Stimulus controllers

Some components require Stimulus controllers for interactive functionality. You can enable only the controllers you need.

Add the controllers to your assets/controllers.json (or assets/admin/controllers.json for admin-only):

{
  "controllers": {
    "@reactic/base-ui": {
      "accordion": {
        "enabled": true,
        "fetch": "eager",
        "webpackMode": "eager"
      },
      "link": {
        "enabled": true,
        "fetch": "eager",
        "webpackMode": "eager"
      }
    }
  }
}

Available controllers:

  • accordion - Required for Accordion component interactive behavior
  • link - Required for Link component disabled state handling

Note: You can enable only the controllers you need. For example, if you only use the Button component (which doesn't require a controller), you don't need to register any controllers.

Alternative: Using AssetMapper

If you're using Symfony AssetMapper instead of Webpack Encore, you need to manually configure the Stimulus controllers.

1. Add controllers to your importmap

Add the following to your importmap.php:

// importmap.php
return [
    // ... other imports

    '@reactic/base-ui/accordion' => [
        'path' => 'vendor/reactic/base-ui/assets/controllers/accordion_controller.js',
    ],
    '@reactic/base-ui/link' => [
        'path' => 'vendor/reactic/base-ui/assets/controllers/link_controller.js',
    ],
];

2. Register controllers in your Stimulus app

Import and register the controllers in your main JavaScript file:

// assets/app.js
import { Application } from '@hotwired/stimulus';

// Import Base UI controllers
import AccordionController from '@reactic/base-ui/accordion';
import LinkController from '@reactic/base-ui/link';

// Start Stimulus
const app = Application.start();

// Register Base UI controllers
app.register('reactic--base-ui--accordion', AccordionController);
app.register('reactic--base-ui--link', LinkController);

Important: Use the exact controller names (reactic--base-ui--accordion, reactic--base-ui--link) to match the data-controller attributes in the templates.

3. Import CSS (optional)

If you want to use the optional pre-configured styles:

// assets/app.js
import 'vendor/reactic/base-ui/assets/styles/accordion.css';
import 'vendor/reactic/base-ui/assets/components/Link/link.css';

Or add them directly in your importmap.php:

'@reactic/base-ui/styles/accordion' => [
    'path' => 'vendor/reactic/base-ui/assets/styles/accordion.css',
],
'@reactic/base-ui/components/link' => [
    'path' => 'vendor/reactic/base-ui/assets/components/Link/link.css',
],

Configuration

No configuration is required! The bundle works out of the box.

The bundle automatically:

  • Registers Twig templates under the @BaseUi namespace
  • Configures Twig Components under the BaseUI: prefix
  • Loads Stimulus controllers for interactive components

Optional: IDE support and template overriding

To enable better IDE support and allow template overriding, add this configuration to config/packages/twig_component.yaml:

twig_component:
    defaults:
        # Map component namespace to template path
        Reactic\BaseUi\Component\: '@BaseUi/components/BaseUI/'

Benefits:

  • IDE autocomplete for component templates
  • Override templates by creating files in templates/bundles/BaseUi/components/BaseUI/
  • ✅ Better refactoring support in your IDE

Example override:

templates/
└── bundles/
    └── BaseUi/
        └── components/
            └── BaseUI/
                └── Button/
                    └── Button.html.twig  # Your custom button template

Available Components

  • Button - Flexible button component with disabled and focus states
  • Link - Navigation link with disabled states, external links, and active page indication
  • Accordion - Collapsible accordion with keyboard navigation
  • Separator - Semantic divider component

Button

A fully accessible button component with flexible rendering options.

Props:

  • disabled (bool): Disable the button (default: false)
  • focusableWhenDisabled (bool): Keep button focusable when disabled (default: false)
  • tag (string): HTML tag to render (button, a, div, etc.) (default: button)
  • type (string): Button type attribute (default: button)
  • label (string): Button text (default: click me)

Example:

{# Basic button #}
<twig:BaseUI:Button label="Click me" />

{# Disabled button #}
<twig:BaseUI:Button label="Save" disabled />

{# Button with custom content #}
<twig:BaseUI:Button>
    <svg>...</svg>
    Save Changes
</twig:BaseUI:Button>

{# Render as link #}
<twig:BaseUI:Button tag="a" label="Learn More" />

{# With custom classes #}
<twig:BaseUI:Button
    label="Submit"
    class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded"
/>

Accordion

A collapsible accordion component with full keyboard navigation and ARIA support.

Components:

  • BaseUI:Accordion - Container
  • BaseUI:AccordionItem - Individual accordion item
  • BaseUI:AccordionHeader - Header wrapper
  • BaseUI:AccordionTrigger - Clickable trigger
  • BaseUI:AccordionPanel - Collapsible content

Example:

<twig:BaseUI:Accordion>
    <twig:BaseUI:AccordionItem value="item-1">
        <twig:BaseUI:AccordionHeader>
            <twig:BaseUI:AccordionTrigger>
                What is Base UI?
            </twig:BaseUI:AccordionTrigger>
        </twig:BaseUI:AccordionHeader>
        <twig:BaseUI:AccordionPanel>
            Base UI is a headless component library that provides
            unstyled, accessible components.
        </twig:BaseUI:AccordionPanel>
    </twig:BaseUI:AccordionItem>

    <twig:BaseUI:AccordionItem value="item-2">
        <twig:BaseUI:AccordionHeader>
            <twig:BaseUI:AccordionTrigger>
                How do I style it?
            </twig:BaseUI:AccordionTrigger>
        </twig:BaseUI:AccordionHeader>
        <twig:BaseUI:AccordionPanel>
            Use CSS classes, Tailwind, or any styling solution you prefer!
        </twig:BaseUI:AccordionPanel>
    </twig:BaseUI:AccordionItem>
</twig:BaseUI:Accordion>

With Tailwind styling:

<twig:BaseUI:Accordion class="space-y-2">
    <twig:BaseUI:AccordionItem value="faq-1" class="border rounded-lg">
        <twig:BaseUI:AccordionHeader>
            <twig:BaseUI:AccordionTrigger class="w-full px-4 py-3 text-left font-medium hover:bg-gray-50">
                Question 1
            </twig:BaseUI:AccordionTrigger>
        </twig:BaseUI:AccordionHeader>
        <twig:BaseUI:AccordionPanel class="px-4 py-3 text-gray-600">
            Answer 1
        </twig:BaseUI:AccordionPanel>
    </twig:BaseUI:AccordionItem>
</twig:BaseUI:Accordion>

Link

A fully accessible navigation link component with support for disabled states, external links, and active page indication.

Props:

  • href (string, required): The destination URL
  • external (bool): External link (adds target="_blank" and security attributes)
  • disabled (bool): Disable the link and prevent navigation
  • active (bool): Mark as current page (adds aria-current="page")
  • underline (string): Underline control (always, hover, none)
  • target (string): Where to open the link
  • download (string|bool): Force download with optional filename
  • rel (string): Link relationship
  • title (string): Tooltip text

Example:

{# Basic link #}
<twig:BaseUI:Link href="/about">About Us</twig:BaseUI:Link>

{# External link (opens in new tab with security) #}
<twig:BaseUI:Link href="https://example.com" external="true">
    Visit Example
</twig:BaseUI:Link>

{# Disabled link #}
<twig:BaseUI:Link href="/premium" disabled="true" title="Upgrade required">
    Premium Feature
</twig:BaseUI:Link>

{# Active link (current page) #}
<twig:BaseUI:Link href="/dashboard" active="true">
    Dashboard
</twig:BaseUI:Link>

{# With custom styling #}
<twig:BaseUI:Link
    href="/contact"
    class="text-blue-600 hover:text-blue-800 underline"
>
    Contact
</twig:BaseUI:Link>

Requires Stimulus controller - See Setup section above.

Separator

A semantic separator/divider component.

Example:

{# Horizontal separator #}
<twig:BaseUI:Separator />

{# With custom styling #}
<twig:BaseUI:Separator class="my-8 border-gray-300" />

Styling Components

All components are completely unstyled by default. This gives you full control over the appearance.

Pre-configured styles (optional)

The bundle includes optional pre-configured CSS styles for each component. You can import them on-demand in your JavaScript entrypoint:

// Import styles for specific components
import '@reactic/base-ui/styles/accordion.css';
import '@reactic/base-ui/styles/separator.css';
import '@reactic/base-ui/components/Link/link.css';

Available style files:

  • @reactic/base-ui/styles/accordion.css - Basic accordion styling
  • @reactic/base-ui/styles/separator.css - Separator/divider styling
  • @reactic/base-ui/components/Link/link.css - Link component styling (disabled states, external links, etc.)

These styles provide a minimal, functional design that you can use as-is or customize to match your design system.

Using CSS Classes

<twig:BaseUI:Button
    label="Click me"
    class="btn btn-primary"
/>

Using Tailwind CSS

<twig:BaseUI:Button
    label="Submit"
    class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded"
/>

Using Data Attributes

Components expose data attributes for styling hooks:

<twig:BaseUI:Button label="Click me" />
{# Renders: <button data-component="button" data-disabled="false">...</button> #}
/* Style via data attributes */
[data-component="button"] {
    padding: 0.5rem 1rem;
    border-radius: 0.25rem;
}

[data-component="button"][data-disabled="true"] {
    opacity: 0.5;
    cursor: not-allowed;
}

Advanced Usage

Custom Component Content

All components support custom content via blocks:

<twig:BaseUI:Button>
    {% block content %}
        <svg class="w-4 h-4 mr-2">...</svg>
        <span>Save</span>
    {% endblock %}
</twig:BaseUI:Button>

Passing Additional Attributes

Use the attributes object to pass any HTML attribute:

<twig:BaseUI:Button
    label="Click"
    id="my-button"
    data-action="click->modal#open"
    aria-label="Open modal"
/>

Development

Project Structure

bundle/base-ui/
├── assets/
│   ├── controllers/          # Stimulus controllers
│   │   ├── accordion_controller.js
│   │   └── link_controller.js
│   ├── components/Link/     # Link component assets
│   │   └── link.css
│   └── styles/              # Optional CSS examples
│       ├── accordion.css
│       └── separator.css
├── config/
│   └── services.php         # Service configuration
├── src/
│   ├── BaseUiBundle.php     # Bundle class
│   └── Component/           # PHP component classes
│       ├── Accordion/
│       ├── Button/
│       ├── Link/
│       └── Separator/
├── templates/
│   ├── components/BaseUI/   # Twig templates
│   └── exemple/             # Usage examples
└── composer.json

Adding New Components

  1. Create PHP component class:
// src/Component/MyComponent/MyComponent.php
namespace Reactic\BaseUi\Component\MyComponent;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent(
    name: 'BaseUI:MyComponent',
    template: '@BaseUi/components/BaseUI/MyComponent/MyComponent.html.twig'
)]
class MyComponent
{
    public string $myProp = 'default value';
}
  1. Create Twig template:
{# templates/components/BaseUI/MyComponent/MyComponent.html.twig #}
<div {{ attributes }}>
    {{ myProp }}
</div>
  1. Use in your templates:
<twig:BaseUI:MyComponent myProp="Hello!" />

Accessibility

All components follow WAI-ARIA best practices:

  • Button: Proper ARIA attributes, keyboard support, focus management
  • Accordion: ARIA expanded/collapsed states, keyboard navigation (Arrow Up/Down, Home/End)
  • Separator: Semantic <hr> with proper ARIA role

License

MIT License - see LICENSE file for details

Author

Nicolas Facciolo Email: nicolas@reactic.io

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

For issues, questions, or suggestions, please open an issue on the project repository.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors