krystal-sf/ux-common
A shared UX toolkit for Laravel/PHP projects, offering common UI helpers and conventions that keep interfaces consistent across Krystal applications. Use it as a lightweight foundation for reusable front-end patterns and smoother developer workflows.
Before you write a single line of integration code, confirm what you actually have: despite being listed under a Laravel evaluation, krystal-sf/ux-common is a Symfony UX companion package (Twig templates, Stimulus controllers, front-end assets, possibly a *Bundle class + DI extension). For a Laravel developer the day-to-day leverage is real but narrow — you are harvesting assets and patterns, not installing a Laravel package.
Minimal steps:
require (unless you're on Symfony). git clone https://gitlab.com/krystal-sf/ux-common and read the tree. In a Laravel app, composer require pulls symfony/framework-bundle, twig/twig, and symfony/ux-* into a container you will never use — redundant at best, resolution conflicts at worst.composer.json (version constraints, license, dev-main vs tagged, what symfony/ux-* packages it pins), README.md, src/ (is there a *Bundle.php, a DI Extension, or just helpers?), templates/ (Twig components), and assets/ or resources/js (Stimulus controllers — the portable gold).config/bundles.php if one exists; otherwise register the Twig namespace in config/packages/twig.yaml (paths: { vendor path: ux_common }) and the asset path in importmap.php (AssetMapper) or webpack.config.js (Encore), then php bin/console assets:install.resources/js/controllers/, styles into resources/css/, and port any Twig components one-to-one into anonymous Blade components under resources/views/components/.First use cases that actually pay off:
resources/js/controllers/ and register it in resources/js/app.js with app.register('name', Controller).Pick one of three modes up front and be explicit in your PR — the wrong mode is where the time gets lost.
Mode A — Read-only reference (recommended default). Never add the package as a dependency. Inspect, extract, and own:
resources/js/ux-common/controllers/ # copied Stimulus controllers, one file each
resources/css/ux-common/ # base styles / tokens
resources/js/app.js # import and register explicitly
Keep the copied files under a single subdirectory and re-export from a thin resources/js/ux-common/index.js so a future upstream diff is a directory-level compare, not a grep across your app.
Mode B — Private fork + path repo. If the portable slice is substantial, fork into a private GitLab repo, add a composer.json with your own version, and consume via a Composer path or vcs repository without pulling Symfony deps (strip the bundle class, DI extension, and symfony/* requires). Pin a commit SHA for provenance. Assign an owner and a changelog. This is the only mode where you can schedule fixes.
Mode C — Full Symfony UX consumption. Only if you actually run a Symfony app: bundle registration, Twig namespace, AssetMapper/Encore wiring, and assets:install. If you're also introducing Turbo, do not mix it with Livewire in the same app — Turbo's navigation model and Livewire's SPA navigation fight each other in ways that reproduce as intermittent, hard-to-trace UI bugs.
Twig → Blade translation pattern. For each Twig component, create a matching anonymous Blade component. Name the slot/props identically so the mental model stays one-to-one:
{{-- resources/views/components/ux/button.blade.php --}}
<button {{ $attributes->merge(['class' => "ux-btn ux-btn--{$variant}"]) }}>
{{ $slot }}
</button>
Keep the CSS class names exactly as upstream ships them — that's what keeps the ported CSS working without edit. Only rename when you have a specific reason, and note every rename in a PORTING.md.
Stimulus registration pattern. Copy controllers verbatim, then register explicitly rather than relying on controllers/index.js auto-discovery (that convention belongs to Symfony's StimulusBundle, not your Laravel app):
import { application } from './bootstrap';
import UxDropdown from './ux-common/controllers/dropdown_controller';
application.register('ux-dropdown', UxDropdown);
Integration tips.
README, PORTING.md, and a last-synced commit SHA — before the first PR lands, or the next developer will assume it's upgradeable.composer require in a Laravel app drags Symfony FrameworkBundle, Twig, and UX packages into a container that already has equivalents. Resolution conflicts and orphaned-service overhead are the practical cost; asset vendoring sidesteps both.dev-main consumption and pin a commit SHA on day one. Upstream bumps to Symfony or Twig majors land with no notice and no migration guide.ux_common) — the default templates/ namespace collides with app templates unpredictably.yarn encore dev), AssetMapper (importmap:install + assets:install), and Vite (npm run dev) have completely different idioms. Nothing in this package will "just work" across them; the copy into Vite is manual and should be.bin/console debug:twig lists namespaces; bin/console debug:container | grep ux_common confirms the bundle loaded; assets:install --symlink reveals the real asset path.php artisan view:clear after copying components (stale compiled views are the #1 "why isn't my component rendering" cause); check resources/js/app.js actually registers the controller; check Vite's build.rollupOptions.input includes the copied subdirectory.templates/bundles/UxCommonBundle/ (Symfony convention).config/packages/ux_common.yaml may expose knobs; read the extension class before assuming defaults.Net for a Laravel developer: treat this as a pattern and asset source, not a dependency. One week of triage and a single component spike is the entire evaluation; if the portable slice is small or overlaps your existing design system, build internally and log the pattern as the takeaway.
How can I help you explore Laravel packages today?