{"schemaVersion":"1.0","type":"Article","slug":"magnify-your-choices-a-fisheye-select-component-for-mantine-5plzy","url":"https://api.zyvop.com/magnify-your-choices-a-fisheye-select-component-for-mantine-5plzy","title":"Magnify Your Choices: A Fisheye Select Component for Mantine","subtitle":null,"tldr":"A select component where items magnify under your cursor -- macOS Dock-style -- built for Mantine...","keywords":["reactjsdevelopment","TypeScript","mantine","nextjs"],"entities":["Giovambattista Fazioli","Senior Full-stack Engineer, Lead Developer","reactjsdevelopment","TypeScript","mantine","nextjs","ZyVOP"],"keyTakeaways":["A select component where items magnify under your cursor -- macOS Dock-style -- built for Mantine 9.","The Problem With Flat Lists You are building a settings panel where users pick a value from a range -- say, a volume level from 1 to 10, or a date from a multi-year timeline.","A traditional &lt;select&gt; dropdown works, but it is buried behind a click."],"headings":["The Problem With Flat Lists","What Is LensSelect?","Key Features","Pill Mode -- Zero Configuration","Count Mode with Ranges","Custom Data with Rich Content","Orientation: Horizontal and Vertical","Visual Effects Stack","Expand on Hover","Selection Modes and Navigation","The Indicator: A Compound Component","renderItem for Total Control","Getting Started","Props and API at a Glance","Advanced Usage","macOS Dock Recreation","Weekday Picker with Dynamic Colors","Volume / Rating Selector","Vertical Timeline","Accessibility","Under the Hood","Live Demo","Links"],"outboundLinks":["https://gfazioli.github.io/mantine-lens-select/","https://www.npmjs.com/package/@gfazioli/mantine-lens-select","https://github.com/gfazioli/mantine-lens-select","https://mantine-extensions.vercel.app/"],"contentText":"A select component where items magnify under your cursor -- macOS Dock-style -- built for Mantine 9. The Problem With Flat Lists You are building a settings panel where users pick a value from a range -- say, a volume level from 1 to 10, or a date from a multi-year timeline. A traditional &lt;select&gt; dropdown works, but it is buried behind a click. A slider works, but it carries no visual weight. What if the selection itself could respond to the user's intent -- items growing as the cursor approaches, then settling back as it moves away? That is exactly what @gfazioli/mantine-lens-select does. It brings the macOS Dock's fisheye magnification effect to a Mantine select component, making selection both functional and delightful. What Is LensSelect? LensSelect is a React component built on top of Mantine 9 that displays a row (or column) of items with a cosine-based lens magnification effect on hover. Items near the cursor scale up smoothly while distant items remain at their base size, creating an interactive and visually engaging selection experience. It ships as @gfazioli/mantine-lens-select v1.0.0, follows the full Mantine Factory pattern, and supports the Styles API out of the box. Key Features Pill Mode -- Zero Configuration The simplest usage needs no data array at all. Just pass a count and you get styled pills with automatic numeric values: import { LensSelect } from '@gfazioli/mantine-lens-select'; function Demo() {return &lt;LensSelect count={20} /&gt;;} Enter fullscreen mode Exit fullscreen mode Twenty rounded vertical bars appear, each responding to your cursor with the fisheye effect. The active pill gets the primary color, hovered pills get a lighter tint, and a dot indicator tracks the selection. Count Mode with Ranges For numeric selection, combine count with min/max to generate evenly distributed values, or use step for fixed increments -- an API that will feel familiar if you have used Mantine's Slider: // 20 pills mapped linearly from 0 to 100&lt;LensSelect count={20} min={0} max={100} /&gt; // Fixed steps: pills at 0, 10, 20, ..., 100&lt;LensSelect min={0} max={100} step={10} /&gt; // Decimal precision: 0.00, 0.25, 0.50, 0.75, 1.00&lt;LensSelect min={0} max={1} step={0.25} precision={2} /&gt; Enter fullscreen mode Exit fullscreen mode The data prop always takes priority, so you can start with count mode during prototyping and switch to a custom data array later without changing anything else. Custom Data with Rich Content Pass an array of { value, view } objects for full control over what each item renders: const apps = [{ value: 'finder', view: &lt;span&gt;📁&lt;/span&gt; },{ value: 'safari', view: &lt;span&gt;🧭&lt;/span&gt; },{ value: 'mail', view: &lt;span&gt;📧&lt;/span&gt; },{ value: 'settings', view: &lt;span&gt;⚙️&lt;/span&gt; },]; &lt;LensSelect data={apps} itemSize={48} magnification={2.5} expandOnHover /&gt; Enter fullscreen mode Exit fullscreen mode When view is omitted from all items, the component automatically enters pill mode -- no special flag needed. Orientation: Horizontal and Vertical Switch between horizontal and vertical layouts with a single prop. Vertical mode is perfect for timeline selectors: &lt;LensSelectdata={events}orientation=\"vertical\"itemSize={12}gap={6}pillWidth={2}activeColor=\"orange\"/&gt; Enter fullscreen mode Exit fullscreen mode Visual Effects Stack Three independent visual effects can be combined: Scale (withScale) -- the core fisheye magnification, enabled by default Opacity (withOpacity) -- distant items fade, focused items stay opaque Blur (withBlur) -- distant items blur, focused items stay sharp Each effect has configurable ranges (opacityRange, blurRange) and all respect the same cosine-based falloff curve controlled by magnification and lensRange. Expand on Hover Enable expandOnHover to make items push their neighbors apart during magnification -- the signature macOS Dock behavior: &lt;LensSelectcount={15}itemSize={48}magnification={2.5}expandOnHovertransitionDuration={0}easing=\"linear\"/&gt; Enter fullscreen mode Exit fullscreen mode Selection Modes and Navigation Two selection modes serve different use cases: Click mode (default) -- user clicks to select Hover mode -- selection follows the cursor automatically On top of that, LensSelect supports: Keyboard navigation -- Arrow keys, Home, End with proper orientation awareness Mouse wheel -- scroll through items with withWheel Loop -- wrap around from last to first (and vice versa) with loop Touch -- swipe navigation on mobile devices The Indicator: A Compound Component LensSelect.Indicator is a compound sub-component that renders a tracking dot below (or beside, in vertical mode) the active item. It follows the item in real time using requestAnimationFrame, staying in sync even during magnification animations. Use it automatically via the withIndicator prop, or mount it explicitly as a child for full control: &lt;LensSelect data={data} withIndicator={false}&gt;&lt;LensSelect.Indicator variant=\"outline\" color=\"red\" size={10} offset={20}/&gt;&lt;/LensSelect&gt; Enter fullscreen mode Exit fullscreen mode Both LensSelect and LensSelect.Indicator support default and outline variants independently, so you can mix filled pills with an outline indicator dot (or vice versa). renderItem for Total Control When pills and view are not enough, renderItem gives you a callback with the item, its active state, current scale factor, and hover status: &lt;LensSelectdata={apps}renderItem={(item, { active, scale, hovered }) =&gt; ( &lt;Box style={{ borderRadius: 10, background: active ? 'var(--mantine-color-blue-6)' : 'var(--mantine-color-gray-1)', fontSize: 24, }} &gt; {item.view} &lt;/Box&gt;)}/&gt; Enter fullscreen mode Exit fullscreen mode Getting Started Install the package: npm install @gfazioli/mantine-lens-select # or yarn add @gfazioli/mantine-lens-select Enter fullscreen mode Exit fullscreen mode Import styles at the root of your application: import '@gfazioli/mantine-lens-select/styles.css'; Enter fullscreen mode Exit fullscreen mode Layer-aware import is also available: import '@gfazioli/mantine-lens-select/styles.layer.css'; Enter fullscreen mode Exit fullscreen mode Then use it: import { LensSelect } from '@gfazioli/mantine-lens-select'; function App() {const [value, setValue] = useState(5); return ( &lt;LensSelect count={10} value={value} onChange={setValue} /&gt;);} Enter fullscreen mode Exit fullscreen mode Props and API at a Glance Core props: Prop Type Default Description data LensSelectItem[] -- Array of { value, view? } items count number -- Generate N pills with numeric values (no data needed) min / max number -- Range bounds for generated values step number -- Fixed increment between generated values precision number 0 Decimal places for generated values value / defaultValue `string \\ number` -- onChange (value) =&gt; void -- Selection change callback orientation `'horizontal' \\ 'vertical'` 'horizontal' magnification number 2 Maximum scale factor (200%) lensRange number 3 Number of adjacent items influenced expandOnHover boolean false Push neighbors apart (Dock behavior) selectionMode `'click' \\ 'hover'` 'click' withWheel boolean false Enable scroll wheel navigation loop boolean false Wrap-around navigation variant `'default' \\ 'outline'` 'default' Visual effects: withScale, withOpacity, withBlur, opacityRange, blurRange Pill styling: itemSize, gap, pillWidth, pillHeight, pillRadius, pillColor, hoverColor, activeColor Transitions: transitionDuration, easing (linear, ease-out, ease-in-out, spring, or custom cubic-bezier) Styles API: 6 selectors (root, track, item, itemContent, itemPill, indicator) and 12 CSS custom properties for full visual customization. Advanced Usage macOS Dock Recreation Combine renderItem, expandOnHover, and a Paper container for a faithful Dock clone: &lt;Paper px=\"lg\" pt=\"md\" pb=\"sm\" radius=\"lg\" withBorderstyle={{ backdropFilter: 'blur(20px)' }}&gt;&lt;LensSelect data={apps} itemSize={48} gap={4} magnification={2.5} lensRange={3} expandOnHover transitionDuration={0} easing=\"linear\" renderItem={(item, { active }) =&gt; ( &lt;Box style={{ borderRadius: 10, background: active ? 'var(--mantine-color-blue-6)' : 'var(--mantine-color-gray-1)', fontSize: 24, }}&gt; {item.view} &lt;/Box&gt; )} indicatorProps={{ size: 4, offset: 8 }}/&gt;&lt;/Paper&gt; Enter fullscreen mode Exit fullscreen mode Weekday Picker with Dynamic Colors Leverage controlled state to change colors based on the selected value: function WeekdayPicker() {const [day, setDay] = useState('Mon');const isWeekend = day === 'Sat' || day === 'Sun'; return ( &lt;LensSelect data={DAYS} value={day} onChange={setDay} itemSize={36} magnification={1.8} lensRange={2} activeColor={isWeekend ? 'red' : 'blue'} indicatorProps={{ color: isWeekend ? 'red' : 'blue' }} /&gt;);} Enter fullscreen mode Exit fullscreen mode Volume / Rating Selector A compact numeric selector with count mode -- no data array required: function VolumeControl() {const [volume, setVolume] = useState(5); return ( &lt;LensSelect count={10} value={volume} onChange={setVolume} itemSize={32} gap={3} pillWidth={6} magnification={1.5} lensRange={2} activeColor=\"teal\" hoverColor=\"teal\" indicatorProps={{ color: 'teal', size: 4 }} /&gt;);} Enter fullscreen mode Exit fullscreen mode Vertical Timeline Use vertical orientation for a time-machine-style date navigator: const events = [2022, 2023, 2024, 2025].flatMap((year) =&gt;months.map((month) =&gt; ({ value: `${month} ${year}` }))); &lt;LensSelectdata={events}orientation=\"vertical\"itemSize={12}gap={6}pillWidth={2}magnification={1.5}lensRange={2}activeColor=\"orange\"indicatorProps={{ color: 'orange', size: 5, offset: 12 }}/&gt; Enter fullscreen mode Exit fullscreen mode Accessibility LensSelect implements the WAI-ARIA listbox pattern: role=\"listbox\" on the root, role=\"option\" on each item aria-selected reflects the current selection aria-orientation matches the layout direction Full keyboard navigation: Arrow keys (orientation-aware), Home, End prefers-reduced-motion media query disables all CSS transitions Configurable ariaLabel prop Under the Hood A few implementation details worth noting: Cosine-based falloff: the lens effect uses (1 + cos(pi * distance / maxRange)) / 2 for a smooth bell-curve that feels natural rather than linear CSS-native responsive props: itemSize, gap, pillWidth, and pillHeight all support Mantine's StyleProp responsive syntax via InlineStyles + CSS media queries -- no useMatches re-renders GPU-optimized: will-change is set only during active hover and removed on mouse leave RAF-synced indicator: the dot position updates via requestAnimationFrame to stay in sync with CSS transitions Live Demo Try the interactive configurator and all use-case demos on the documentation site: gfazioli.github.io/mantine-lens-select Links Documentation &amp; Demos npm package GitHub repository All Mantine Extensions","contentHash":"sha256:0aa430113492a2a8213a82d9326ca88ec601832049aee665d038d01b8590c689","authorName":"Giovambattista Fazioli","authorUrl":"https://api.zyvop.com/author/giovambattista","authorSameAs":["https://gfazioli.github.io","https://github.com/gfazioli","https://x.com/gfazioli","https://www.linkedin.com/in/giovambattistafazioli/"],"category":null,"tags":["reactjsdevelopment","TypeScript","mantine","nextjs"],"audience":"Readers researching reactjsdevelopment","tone":"Professional, senior full-stack engineer, lead developer perspective","readingTimeMinutes":7,"wordCount":1536,"faqs":null,"primaryTopic":"reactjsdevelopment","publishedAt":"2026-09-04T09:26:02.876Z","updatedAt":"2026-09-04T09:26:02.876Z","canonicalUrl":"https://dev.to/undolog/magnify-your-choices-a-fisheye-select-component-for-mantine-4plm"}