Mischief

01 / Controls

Magnetic Tabs

Familiar tabs with a gentle pull toward the pointer. Selection stays clear and keyboard navigation remains immediate.

Everything important is close by.

Your download is ready and notifications are on.

Installation

Copy the source into your project, or keep it behind a package.

npx shadcn@latest add Tinkerers-Labs/mischief-ui/magnetic-tabs
import { MagneticTabs } from "mischief-ui/magnetic-tabs"
Also installs
  • @base-ui/react
  • motion

Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.

registry/default/magnetic-tabs/magnetic-tabs.tsx
"use client" import * as React from "react"import { Tabs } from "@base-ui/react/tabs"import {  motion,  useMotionValue,  useReducedMotion,  useSpring,} from "motion/react"import { cn } from "@/lib/utils" export interface MagneticTabItem {  value: string

Usage

const items = [
  { value: "overview", label: "Overview", content: <p>Ready to go.</p> },
  { value: "activity", label: "Activity", content: <p>No new activity.</p> },
]

export function Example() {
  return <MagneticTabs items={items} />
}

What it needs installed

This is one of the seven components that reach for something beyond React. Base UI supplies the tab semantics -- roving focus, the tab and panel relationship, arrow key movement -- and Motion drives the indicator. Both are optional peers, so they are only installed if you ask for them.

npm install mischief-ui @base-ui/react motion
Import it from its own entry: mischief-ui/magnetic-tabs, not the package root.

The root import deliberately does not carry it, because a barrel holding it would fail for everyone who had not installed those two. That is the trade: a subpath import here, and no unexpected dependencies anywhere else.

The magnetism, and doing without it

The indicator is spring-driven and leans towards the pointer as it moves across a tab, then settles when the pointer leaves. It is a stiff, light spring, so it arrives quickly rather than wobbling -- the effect should read as responsive, not bouncy.

When the reader has asked for reduced motion the lean is not applied at all and the indicator moves straight to the selected tab. Nothing about which tab is selected, or how it is reached from the keyboard, depends on any of this.

API

itemsMagneticTabItem[]Labels, values, panels, and disabled states.
defaultValuestringThe initially selected tab.
valuestringThe selected value when controlled.
onValueChange(value: string) => voidRuns when selection changes.
classNamestringClasses for the root element.

MagneticTabItem

valuestringIdentifies the tab. What value and onValueChange speak in.
labelReactNodeThe tab itself.
contentReactNodeThe panel shown while the tab is selected.
disabledbooleanListed but unselectable, and skipped by the arrow keys.

Accessibility

Base UI supplies tab semantics, arrow-key navigation, focus handling, and panel relationships. Pointer attraction is removed when reduced motion is enabled.