Skip to content

Why a Filament select dropdown gets cut off ​

A Filament select dropdown gets cut off when some ancestor element has overflow set to anything other than visible. The option panel is not a browser popup, it is an absolutely positioned div rendered inside the same wrapper as the input, so a single overflow: hidden two levels up, often one you added yourself for rounded corners, clips it to the input box. We shipped that bug in FilamentCraft's section builder and could not see it in house, because it takes a non-native select to reproduce.

This post is the diagnosis: where the panel actually lives in the DOM, why a native select masks the problem, and the related trap where backdrop-filter swallows a modal that is position: fixed.

Where a Filament select dropdown lives in the DOM ​

Filament renders both halves of a select inside x-filament::input.wrapper, which outputs the .fi-input-wrp element you see around every field. Open vendor/filament/forms/resources/views/components/select.blade.php and the structure is one if:

blade
<x-filament::input.wrapper ...>
    @if ($isNative)
        <select ...>
            {{-- real <option> elements --}}
        </select>
    @else
        <div x-data="selectFormComponent({ ... })" wire:ignore ...>
            {{-- the Alpine component that builds the panel --}}
        </div>
    @endif
</x-filament::input.wrapper>

The native branch is safe. A real <select> has its option list drawn by the operating system, outside the page's box model entirely, so no CSS on the page can clip it.

The other branch is the one that breaks. The Alpine component builds a panel carrying Filament's fi-dropdown-panel class, and in vendor/filament/support/resources/css/components/dropdown/index.css that class is absolute z-20. An absolutely positioned element is still a DOM descendant of .fi-input-wrp, and descendants get clipped by an ancestor whose overflow is hidden, auto or scroll. Same for contain: paint.

The Filament select docs describe native(false) as the way to opt into the JavaScript select, but the view opts you in on its own too. It computes $isNative = (! ($isSearchable || $isMultiple || $isHtmlAllowed) && $isNative()), so searchable(), multiple() and allowHtml() each force the panel branch whether or not anyone wrote native(false). Two other fields render their floating panel in the same wrapper: the color picker's swatch panel and the date picker's calendar are both x-ref="panel" elements inside the same x-filament::input.wrapper.

This is not specific to us. Filament's own table filters hit it: issue #13824, "Select Filter with Multiple Options Hidden by overflow: hidden CSS in FiltersLayout::BelowContent", is the same mechanism inside the framework's own layout.

How we clipped our own dropdowns ​

FilamentCraft's section builder restyles Filament's form components so they match the editor rather than the admin panel. That skin lives under one class and, until v1.39.1, it started like this:

css
.fc-builder__form-skin .fi-input-wrp,
.fc-builder__form-skin .fi-fo-rich-editor {
    border: 1px solid var(--fc-border);
    border-radius: 0.5rem;
    overflow: hidden;
}

The overflow: hidden was there for the rich editor. Its toolbar sits flush against the top inside the border, and without clipping, the toolbar's square corners poke through the rounded box. Applying the same rule to .fi-input-wrp looked harmless.

The section builder with a block selected and a right-hand inspector listing Layout, Gap, Padding, Alignment, Distribution, Surface, Border, Corners and Shadow selects, each in a rounded outlined box
Every select in that inspector sits in a rounded wrapper. That wrapper is the box that was clipping the option panel.

Nothing broke in house, because every one of those inspector selects is native. Our settings DSL only drops to the non-native renderer when a setting asks for search:

php
use FilamentCraft\Settings\Types\Select;

Select::make('layout')
    ->label('Layout')
    ->options([
        'spotlight' => 'Spotlight',
        'editorial' => 'Editorial',
        'compact' => 'Compact',
    ])
    ->searchable()
    ->default('spotlight');

Add ->searchable() and the field turns into the Alpine panel. So does any raw Filament component a host app returns from a section's settings(), which is supported on purpose. Any host app whose custom section reached for Select::native(false) or searchable() got its dropdown rendered inside our skin: the search box visible, every option below it sheared off at the input's bottom border.

The fix in v1.39.1 was to stop treating the two elements as one. Only the element that needs clipping gets it:

css
.fc-builder__form-skin .fi-input-wrp,
.fc-builder__form-skin .fi-fo-rich-editor {
    border: 1px solid var(--fc-border);
    border-radius: 0.5rem;
}

.fc-builder__form-skin .fi-fo-rich-editor {
    overflow: hidden;
}

The settings panel skin had the right shape already: it sets border-radius on .fi-input-wrp and never sets overflow there. A rounded wrapper does not need clipping when the input inside it has a transparent background, which Filament's inputs do.

If you are chasing this in your own panel, the search is short. Look for overflow and contain on anything between .fi-dropdown-panel and the page root: your own form skin, a card component with overflow-hidden rounded-xl, a scrollable sidebar, a table container. The rule that keeps it from coming back is to clip the element that actually needs clipping, never the generic wrapper.

The same shape of bug with position: fixed ​

A month later, in v1.40.0, the section builder hit the sibling version of this. The builder's action modals opened behind the builder itself, clipped and unclickable.

Filament modals are position: fixed. A fixed element normally resolves against the viewport, which is why it can escape any ancestor. It stops escaping when an ancestor has one of the properties that turn it into a containing block. MDN's containing block page lists them: for position: absolute or fixed, the containing block "may also be formed by the edge of the padding box of the nearest ancestor element that has" a non-none filter, backdrop-filter, transform, perspective, rotate, scale or translate, a contain value of layout, paint, strict or content, or a matching will-change.

Our backdrop has one of them:

css
.fc-builder__backdrop {
    position: fixed;
    inset: 0;
    z-index: 60;
    background: color-mix(in srgb, var(--fc-bg-canvas) 55%, transparent);
    backdrop-filter: blur(2px);
}

The modal markup was rendered inside that backdrop. backdrop-filter: blur(2px) made the backdrop the containing block for the modal's fixed positioning, and the backdrop's z-index: 60 buried a modal that only reaches z-index: 50. Two independent reasons for the same symptom, which is why moving the z-index alone did not fix it.

The fix is structural. The modals render as a sibling of the backdrop, in their own stacking context above it:

blade
<div class="fc-builder__backdrop" data-fc-builder-open>
    {{-- the builder --}}
</div>

<div class="fc-builder__modals">
    <x-filament-actions::modals />
</div>
css
.fc-builder__modals {
    position: relative;
    z-index: 65;
}

Both bugs are the same lesson at different scales. A parent property you added for looks, overflow for rounded corners or backdrop-filter for a frosted backdrop, quietly redefines the rules for every descendant that floats out of the flow. Filament's floating panels are descendants, not popups.

Next: the choice inputs guide covers searchable() and the rest of the DSL's select options, and custom sections shows where those settings are declared. You can open the editor and click through the inspector yourself on the live demo.

Last updated: