Weave 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

Dropdown Laravel Package

mrshanebarron/dropdown

A Laravel/PHP package for adding reusable dropdown and select-menu functionality to your application, helping simplify defining, rendering, and managing dropdown options with a clean, framework-friendly API.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and let auto-discovery do the work.

    composer require mrshanebarron/dropdown
    

    This is a standard Laravel package shape (service provider + mergeConfigFrom + publishable config), so Laravel's package discovery should register it without manual wiring. If you have discovery disabled, add the provider to config/app.php yourself.

  2. Publish the config before you write any menus.

    php artisan vendor:publish --provider="MrShaneBarron\\Dropdown\\DropdownServiceProvider" --tag=config
    

    Note the v1.0.1 release: the published file is config/sb-dropdown.php (the sb- vendor prefix was fixed in this patch). If you ever ran v1.0.0, check for an old-prefixed file and remove it — see Gotchas.

  3. Look here first (in order):

    • config/sb-dropdown.php — the whole shape of the API lives here: defaults for classes, wrapper markup, how links/labels/dividers are declared, and any route/authorization flags.
    • The package's Blade components/views (under resources/views in the package source) — since the docs are thin, the actual markup is the real spec. Read it before you fight it.
    • The README/description bullets: nested menus, custom attributes, routing/authorization support, no frontend dependencies.
  4. First use case — an action menu. Start with the smallest thing that proves the pipeline: one dropdown with two links and a divider.

    <x-dropdown>
        <x-dropdown-link :href="route('orders.show', $order)">View</x-dropdown-link>
        <x-dropdown-link :href="route('orders.edit', $order)">Edit</x-dropdown-link>
        <x-dropdown-divider />
        <x-dropdown-link :href="route('orders.destroy', $order)" method="DELETE">Delete</x-dropdown-link>
    </x-dropdown>
    

    (Exact component names/attributes come from the config + views — confirm them there once, then standardize internally.) Render it in a layout, hit the page, and verify the HTML. This package gives you markup and a menu-definition API, not JavaScript — the toggle behavior is yours (see Gotchas).

Implementation Patterns

Define menus once, render them in many places. The package's value is eliminating duplicated navigation markup. For menus used in more than one view (a user/account menu, an admin sidebar submenu), define them in the config or in a small dedicated class/partial, then render from that single source. Treat the config as the menu registry and the Blade components purely as presentation.

Keep menu definitions declarative, keep data out of them. Define structure (labels, dividers, grouping) statically; pass runtime data (models, URLs) in from the view/controller. Putting Order::find() inside a config array is the fastest way to break config:cache.

Gate links at the point of definition, not after render. The description calls out Laravel-friendly authorization support. Prefer the package's built-in authorization hook (e.g. per-link can/policy checks) over wrapping each link in @can in the view — it keeps the menu definition self-contained and makes it reusable across roles. For menus with many permission-dependent items, resolve the allowed menu once per request (a view composer or a small Menu service) rather than re-evaluating gates in nested loops.

Compose with route names, never hardcoded paths. Route names survive URL changes and keep menus working under localized/parameterized routes. If a menu item points at a route that may not exist in every context, resolve it conditionally — a missing route name will throw at render.

Wrap the package in your own layer for consistency. Build one <x-app.dropdown> that composes the package's components with your design-system classes, then use that everywhere. This is the upgrade-safe move: if the package's markup changes or you swap packages, you edit one wrapper, not 40 views. It also lets you enforce consistent data-* hooks for your JS.

Add your own behavior layer (no frontend deps ≠ no JS). Pair the package with a tiny Alpine component or a small vanilla controller to handle open/close, outside-click, and Escape. Standardize the attributes (aria-expanded, aria-haspopup, role="menu") in your wrapper so accessibility isn't per-view tribal knowledge.

Use nested groups for nav, flat menus for actions. Nested menus pay off for sidebar/global nav; for row-level actions, keep them flat — deep nesting in action menus is a usability and keyboard-nav liability.

Test the rendered HTML. $this->get(...)->assertSee(...) plus assertSeeInOrder for dividers/order, and feature tests for authorization: assert an unauthorized user does not see the link. Since rendering is cheap and DB-free, these tests are fast and high-value — they're also your regression net across package upgrades.

Gotchas and Tips

The config filename is the sharp edge from v1.0.1. The fix renamed the published file to the sb- prefix. If you published under v1.0.0:

  • Delete/rename the old file and republish, or you'll have two config files and ambiguous resolution.
  • mergeConfigFrom keys off the new filename, so a stale old file silently masks new defaults — you get no error, just wrong-by-default behavior.
  • Then run php artisan config:clear and rebuild php artisan config:cache. A stale config cache will keep the old key set alive regardless of what's on disk.

This rename step belongs in your deploy/upgrade runbook, not just the PR description — it's the one thing most likely to bite a team.

Pin ^1.0.1, not 1.0.1. The release is a config-path patch; staying inside the 1.x line absorbs further config hygiene fixes without a lockstep bump.

Never ship config-dependent definitions that reference runtime state. Any closure or model access in config/sb-dropdown.php breaks under config:cache (closures can't be serialized) or freezes the first-resolved value. Keep config to primitives and class strings; put dynamic menu construction in a class/view composer.

Authorization checks are per-render. If a menu contains many gated items, you're running a gate/policy per item per render. For hot paths (layouts rendered on every request), memoize the resolved menu for the request instead of letting every layout re-evaluate.

Route cache interacts with menu definitions. Under route:cache, route names are resolved from the compiled table — fine in production, but if you're generating routes conditionally in dev, behavior can differ. Verify menus after route:cache in CI, not only locally.

You own the accessibility and CSS. "No frontend dependencies" means no keydown handling, no focus trap, no aria-* for free. Budget for it explicitly in the wrapper component; retrofitting keyboard navigation across dozens of hand-rolled menus is expensive.

Custom attributes are the extension point — use them deliberately. class, id, and data-* pass-through is how you integrate with your CSS framework and JS. Standardize a small set (data-dropdown, data-dropdown-menu, data-dropdown-item) in your wrapper rather than inventing ad-hoc hooks per page.

Watch markup nesting. Nested groups typically produce nested lists/divs; if your CSS assumes a flat structure (e.g. a single ul > li selector), deeply nested output will render unstyled or mis-indented. Inspect one nested render early and adjust selectors before rolling it out.

Debug by dumping the rendered HTML, not the config. When a menu item doesn't appear or is styled oddly: (1) dd(config('sb-dropdown')) to confirm defaults merged, (2) dd() the rendered component output to confirm markup, (3) only then suspect the package. Most issues land in steps 1–2.

Thin documentation and a single maintainer are real risks. With 0 stars and one-line release notes, the package source is the documentation. Before adopting broadly, read the service provider and views end-to-end, verify the illuminate/support constraint against your Laravel version, and keep your wrapper layer thick enough that swapping the package out is a one-file change.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle