CSS Can Finally Replace Your Tooltip and Dropdown Libraries
For years, two of the most common UI patterns on the web, a tooltip that stays attached to its trigger and a select dropdown you can actually style, have both needed a JavaScript library to do properly. Popper.js, Floating UI, and Tippy.js exist because CSS had no concept of "position this relative to that other element, and flip sides if it would overflow." Custom select components like react-select exist because the native <select> popup has always been a black box no stylesheet can touch.
Both gaps are closing. CSS anchor positioning and appearance: base-select now ship in real browsers, not experimental flags in one engine. Neither has full three-way parity yet, but both are far enough along to change what you reach for on the next component you build.
Anchor positioning: tethering without a library
CSS anchor positioning lets you tie one element's position to another using three pieces: anchor-name on the element being anchored to, position-anchor on the element being positioned, and the anchor() function inside its top/left/right/bottom values.
.trigger {
anchor-name: --tooltip-trigger;
}
.tooltip {
position: absolute;
position-anchor: --tooltip-trigger;
top: anchor(bottom);
left: anchor(left);
}
That's the whole job Popper.js was built to do: compute a floating element's position relative to a reference element, expressed as three CSS declarations instead of a runtime library recalculating on every scroll and resize event. @position-try goes further, letting you define fallback positions the browser switches to automatically when the default would overflow the viewport, the flipping behavior that made Floating UI's autoUpdate necessary in the first place.
The browser support picture, per Chrome for Developers (opens in a new tab) and OddBird's ongoing anchor positioning coverage (opens in a new tab), looks like this: Chrome shipped it in version 125 and it reached Baseline Newly Available in January 2025. Safari added support in version 26. Firefox shipped an implementation behind a flag in version 145, which means it isn't Baseline Newly Available across all three engines yet, only in the two that ship it unflagged.
That's close enough to use, not close enough to rip out a mature tooltip library across an entire app in one pass. The realistic move: reach for anchor positioning on new floating UI, wrapped in an @supports check, and keep the JS library only where you already depend on it for edge cases anchor positioning doesn't cover yet, like elements inside a virtualized list or a deeply nested scroll container.
.tooltip {
display: none;
}
@supports (anchor-name: --x) {
.tooltip {
display: block;
position: absolute;
position-anchor: --tooltip-trigger;
top: anchor(bottom);
left: anchor(left);
}
}
Anchor positioning is also one of the named focus areas in Interop 2025 (opens in a new tab), the annual cross-browser effort Chrome, Firefox, and Safari's engineering teams use to close exactly these kinds of gaps. That's a reasonable signal that the remaining Firefox flag is a matter of time, not a stalled proposal.
Customizable selects: styling what used to be a black box
The second gap is the <select> element. Its trigger has always taken CSS fine. Its popup, the actual list of options, has never been reachable by a stylesheet, which is why teams have spent a decade reaching for react-select, downshift, or a hand-rolled combobox any time a design called for anything beyond the browser default.
appearance: base-select changes that:
select,
::picker(select) {
appearance: base-select;
}
::picker(select) {
border-radius: 8px;
box-shadow: 0 8px 24px rgb(0 0 0 / 0.15);
}
select option:checked::before {
content: '✓';
}
Setting appearance: base-select on both the <select> and its ::picker(select) pseudo-element opts the control into a fully stylable mode. The element is still a real <select>, which matters more than it sounds: keyboard navigation, type-ahead, focus management, and the accessibility tree all keep working the way a native form control's are expected to, none of it needs reimplementing the way it does in a from-scratch listbox component.
Per Chrome for Developers' announcement post (opens in a new tab) and its follow-up findings from developer feedback (opens in a new tab), the feature is available starting in Chrome 135 and Safari 27. Firefox doesn't support it yet, but it fails safe: an unsupported browser just ignores the appearance value and renders the ordinary native select, unstyled but fully functional. That makes it one of the lowest-risk progressive enhancements available right now, closer in spirit to a CSS fallback than a feature you need a JS shim to polyfill.
What matters more than the styling freedom is what you don't have to rebuild. A hand-rolled combobox has to reimplement arrow-key navigation, type-ahead-to-jump, aria-activedescendant wiring, and the focus behavior a screen reader expects from a listbox, and it's easy to get subtly wrong in ways that don't show up until an accessibility audit. A <select> styled with base-select is still the same element under the hood, so none of that behavior needs rebuilding or re-testing. You're styling a native control, not a <div> dressed up to look like one.
How to actually adopt these
The mistake to avoid is waiting for full three-engine support before touching either feature, or the opposite mistake of ripping out a working library across an entire codebase before the gaps close. Neither serves a team well.
Gate both behind @supports. @supports (anchor-name: --x) and @supports (appearance: base-select) ship the CSS-native version to browsers that support it and fall back cleanly everywhere else, with no user-agent sniffing required. Start with new components rather than retrofits: a new tooltip or a new filter dropdown is a much safer place to try anchor positioning or base-select than reworking an existing, load-bearing component library in one migration.
Keep the JS library where it's actually earning its place. Complex collision detection, virtualized-list anchoring, and multi-select comboboxes with custom filtering logic are still legitimately harder in pure CSS today, so don't delete a dependency you still need just because a newer primitive exists. And track the Firefox gap rather than the calendar. Firefox 145 shipping anchor positioning behind a flag is the signal to watch, not a fixed date. Once it ships unflagged and base-select lands in Firefox too, the remaining case for the JS dependency in most apps disappears.
Neither feature is a wholesale replacement yet. Both are further along than most teams have noticed, and the components built today are exactly the ones that'll benefit from being written the native way from the start, instead of migrated off a library later.