symfony/ux-toggle-password
Symfony UX TogglePassword adds an accessible show/hide control to password fields, letting users reveal what they type. It integrates with Symfony Forms and Twig, requires minimal setup, and offers a simple, user-friendly way to reduce password entry errors.
Framing correction up front: symfony/ux-toggle-password is not a Laravel package. It is a Symfony UX package — a Composer library containing a small Stimulus controller, a Twig form theme fragment, and a PHP-side integration into Symfony's Form component and symfony/stimulus-bundle. For a Laravel shop, the only genuinely reusable artifact is the JavaScript controller (roughly a few dozen lines). The PHP integration is inert outside Symfony. This assessment therefore evaluates it two ways: as a direct dependency in a Symfony codebase, and as a reference/vendored asset in a Laravel codebase.
Architecture fit. The package's design is narrow and correct: one Stimulus controller, one accessible button, progressive enhancement over an existing <input type="password">. It assumes Symfony Forms rendering through Twig with a form theme, Stimulus loaded via symfony/stimulus-bundle, and an asset pipeline (AssetMapper or Encore). In that environment it is a near-zero-friction fit. In Laravel it does not fit architecturally — Laravel uses Blade, its own form helpers, and Vite; there is no Twig form theme hook and no controllers.json auto-registration. Pulling the Composer package into a Laravel app to obtain ~1 KB of JS would drag in symfony/form, symfony/twig-bridge, and symfony/stimulus-bundle, adding surface area and potential version conflicts with the Symfony components Laravel already pins (console, http-foundation, process). That is a poor trade.
Integration feasibility. High for Symfony (a composer require plus asset build; typically under an hour). Medium for Laravel, and only if we deliberately strip the PHP layer: copy the controller from vendor/ into resources/js/controllers/, register it with the existing Stimulus setup, and build a Blade component that emits the same markup contract (input + type="button" toggle with aria-pressed / aria-controls and a visually hidden label). That is a half-day including tests. Note the package is not published to npm, so there is no clean front-end-only install path; vendoring or copying is the realistic route.
Technical risk. Low code risk, moderate supply-chain and fit risk.
| Risk | Severity | Notes |
|---|---|---|
| Adoption signal | Medium | 26 stars, score ~12.4 — essentially no community validation or third-party production evidence |
| Maintenance continuity | Medium | Small Symfony UX subpackage; viable but dependent on Symfony's UX roadmap and a small maintainer pool |
| Laravel impedance mismatch | Medium | PHP layer unusable; forces copy-and-fork, which severs upgrade path |
| Behavioral edge cases | Low–Med | Toggling type between password/text interacts with password managers (1Password, Bitwarden) and browser-native reveal affordances |
| Accessibility regressions | Low–Med | ARIA is present but must be verified with NVDA/VoiceOver and keyboard-only flows; icon-only buttons need accessible names and localization |
| Security/UX policy | Low | Reveal-on-screen increases shoulder-surfing exposure in shared/kiosk contexts; may need a policy decision |
| Build/CSP | Low | Stimulus uses data attributes, so no inline script; but incorrect controller path silently yields a dead button |
Key questions.
stimulus-use/@hotwired/stimulus via Vite; Symfony: stimulus-bundle version)?Stack fit. Two distinct paths, and the choice should be made before any code is written. Path A (Symfony): install as a normal dependency, enable the form theme, use the form type option, and let stimulus-bundle handle registration — do not hand-roll the asset wiring. Path B (Laravel): treat the package as a reference implementation, not a dependency. Extract assets/src/controller.js, add it to the Vite/Stimulus controller registry, and author a Blade <x-password-input> component that renders the identical markup contract. Path B is the recommended default for this organization given the Laravel framing.
Migration path. Inventory every password input first — login, registration, reset, change-password, admin impersonation, and any "confirm password" modals. Replace incrementally per surface rather than in one sweep. Where bespoke toggle JS exists today, remove it in the same PR as the replacement to avoid double toggles and conflicting aria-pressed state. Keep the original markup as the no-JS fallback (input stays type="password" until the controller connects), so a failed asset build degrades to today's behavior rather than to a broken field.
Compatibility. Confirm Stimulus 3.x, the chosen bundler (Vite for Laravel; AssetMapper or Encore for Symfony), and Turbo/Livewire interaction. With Turbo, the controller reconnects on navigation and is fine; with Livewire, morphing can preserve DOM nodes, so verify the button state survives a re-render and does not desynchronize from the input type. Add a CSP check — no inline handlers should be introduced. Localize labels and icons; do not ship English-only aria-label strings.
Sequencing.
type="button" guard, disabled/pending states.Maintenance. Low ongoing effort, but non-zero ownership. On Path A, upgrades arrive with Symfony UX releases and are usually mechanical. On Path B, we own the fork: pin the copied controller, record the upstream version in a header comment, and schedule a quarterly diff against upstream. Because the controller is small and stable, divergence risk is modest — but nobody upstream is obligated to notify us of ARIA or browser-behavior changes, so the diff must be a real recurring task, not a good intention. Last release 2026-05-29 suggests recent activity; 26 stars suggests it could go quiet without anyone noticing.
Support. Essentially self-support. There is no vendor SLA, no paid tier, and a very small issue tracker population — expect to debug independently. MIT licensing is permissive and safe for commercial use, modification, and redistribution, which is the main thing we need. Internal support burden lands on the front-end owner; document the component in the design system so other teams do not re-implement it.
Scaling. Negligible runtime cost: one controller instance per password field, no server round-trips, no database or cache involvement, no per-request overhead. Bundle impact is on the order of a kilobyte, already amortized if Stimulus is loaded anyway. The scaling concern is organizational, not technical — N teams each copying their own toggle implementation is the realistic failure mode, which is why the Blade component (or form theme) should be the only sanctioned entry point.
Failure modes.
data-toggle-password-target present check).type="button" submits the form on click. Easy to miss in a copy-paste fork; assert in tests.type can cause some managers to re-detect or clear fields. Test with the top two managers in the support matrix.text.aria-pressed not updated. Caught only by audit.Ramp-up. Roughly half a day for a mid-level front-end developer to understand Stimulus conventions and the markup contract; a further half-day for the accessibility test pass. No specialized Symfony knowledge needed on Path B, which is precisely why Path B is attractive. Product/design involvement is limited to the button treatment and the reveal-policy question. Total estimated first-delivery cost across all password surfaces: two to three developer-days including tests and audit, with near-zero recurring run cost.
Recommendation. Do not add this as a Composer dependency in a Laravel application. Treat it as a well-formed, MIT-licensed reference: vendor the controller with provenance, wrap it in a first-party Blade component, and own the accessibility tests. If the target is in fact a Symfony application, adopt it directly — it is exactly the kind of small, focused UX package Symfony is good at, and the low star count is a discoverability artifact rather than a code-quality signal.
How can I help you explore Laravel packages today?