Skip to main content

Fix dropdown top item being clipped on auto-scroll (WEB-1194)

Version: 0.8.0 ยท Type: ๐Ÿ› Bug Fix

WEB-1194

Problemโ€‹

When a Select (the Radix Select branch: composed usage and non-searchable declarative options) opens and the selected option sits far down the list, the dropdown auto-scrolls to reveal it โ€” and the first still-visible item at the top gets clipped, pressed flush against the panel's top edge. It was reproduced on the display-language setting: a Chinese user whose value is "ไธญๆ–‡๏ผˆ็ฎ€ไฝ“๏ผ‰" (index 8) opens the list, it scrolls down, and "Espaรฑol" at the top is cut off.

The root cause is where the vertical padding lived. Radix SelectPrimitive.Viewport ships an inline overflow: hidden auto plus flex: 1, which makes the viewport itself the scroll container (its flex: 1 overrides the h-(--radix-select-trigger-height) class and grows it to the content's max-h-[268px]). The dropdown's top / bottom py-(--Spacing_8) was applied to that viewport โ€” i.e. inside the scroll region. So when the browser scrolled the selected item into view, the 8px top padding scrolled away with the content and the top-most visible item lost its breathing room, landing clipped against the edge.

This surfaced after the earlier refactor that removed SelectScrollUpButton / SelectScrollDownButton and switched the content to a native overflow-y-auto: the scrolling paradigm changed, but the padding was left inside the (now natively scrolling) viewport.

Changed Filesโ€‹

  • packages/design/src/components/Select/styles.ts
  • packages/design/src/components/Select/Select.tsx
  • packages/design/src/components/Select/__tests__/Select.test.tsx

Changesโ€‹

  1. Slim SELECT_VIEWPORT_TOKEN_CLASS down from mergeClass('pd-select__viewport', 'py-(--Spacing_8)', 'overflow-y-auto') to just the semantic marker 'pd-select__viewport': drop the in-scroll py-(--Spacing_8) (moved to the content frame) and the redundant overflow-y-auto (Radix's inline overflow: hidden auto already owns scrolling).
  2. Add py-(--Spacing_8) to the SelectContent content className. Radix applies display:flex; flex-direction:column to the popper content element, so this padding lands on the non-scrolling flex frame and stays fixed.
  3. Bump SELECT_DROPDOWN_MAX_HEIGHT from max-h-[268px] to max-h-[270px]. The content carries a 1px border and Tailwind's preflight sets box-sizing: border-box, so max-height includes the border. At 268px the viewport (flex: 1) was only 268 โˆ’ 2(border) โˆ’ 16(padding) = 250px โ€” less than 7 ร— 36 = 252px โ€” so a later selected option still left a ~2px clip on the top item when scrolled. Counting the 2px border makes the viewport 252px, exactly 7 ร— 36px, so scrolling snaps to item boundaries and no item is clipped.
  4. Leave the shared SELECT_CONTENT_TOKEN_CLASS untouched โ€” it is reused by the searchable Popover (SELECT_POPOVER_CONTENT_CLASS) and MultiSelect (MULTI_SELECT_CONTENT_CLASS), which handle their own padding; scoping the change to SelectContent avoids double padding there.
  5. Add regression tests asserting the vertical padding lives on the listbox (content frame) not the viewport, and that the listbox is max-h-[270px].

The visual of a non-scrolled panel is unchanged (still an 8px gap between the panel border and the first item); only the scroll-clipping is fixed. The searchable / MultiSelect branches are unaffected.