Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Navigation

Proximity Sidebar

Navigation

Proximity Sidebar React component for Tailwind CSS v4

PaginationScroll Progress
Live props
Appearance
side
dashColor
activeColor
dashLength110px
gap8px
thickness1px
Behavior
activeOffset0.4
reach40px
updateHash
Layout
inset32px
Content
label
dashLabel
Code
import ProximitySidebar from "@/components/ui/proximity-sidebar"

<ProximitySidebar />
  1. Home
  2. Components
  3. Navigation
  4. Proximity Sidebar
Free

Proximity Sidebar React component for Tailwind CSS v4

v1.0.1·Updated in 1.2.1

A React table of contents rail: thin dashes that swell near the pointer and track your section.

Scroll through the content and the dash for the current section pulses wide, then settles back once you stop. Move the pointer along the rail to swell the dashes nearest to it, and click a dash to smooth-scroll to its section and update the URL hash (unless updateHash is off). On a phone, touch the rail and slide: the dash under your finger swells, and lifting your finger jumps to it. From the keyboard, Tab onto the rail, move with the arrow keys, Home and End (the focused dash swells), and press Enter to jump.

Proximity Sidebar is a free React component in the React navigation components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add proximity-sidebar, together with Motion presets and UIProvider and the npm package motion.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add proximity-sidebar

Usage

Import ProximitySidebar from @/components/ui/proximity-sidebar.

import ProximitySidebar, { type ProximitySection } from "@/components/ui/proximity-sidebar"

const sections = [
  { id: "intro", label: "Introduction", level: 1 },
  { id: "setup", label: "Setup", level: 2 },
  { id: "api", label: "API", kind: "section" },
  { id: "faq", label: "FAQ", kind: "body" },
] satisfies ProximitySection[]

export function Demo() {
  return (
    <aside className="sticky top-20 h-[70vh]">
      <ProximitySidebar sections={sections} side="left" activeOffset={0.4} />
    </aside>
  )
}

Props

  • sections*
    Array<{ id: string; label: string; kind?: "title" | "subtitle" | "section" | "body"; level?: 1 | 2 | 3 | 4 | 5 | 6 }>

    Ordered section map used for rendering dashes and scroll targeting. Each id must match an element id present in the page. kind (or level 1 to 3) sets the dash length and color; without either, the first heading inside the element decides.

  • sidedefault "left"
    "left" | "right"

    Pins the minimap to the chosen side and flips the dash transform origin to match.

  • activeOffsetdefault 0.4
    number

    Viewport anchor ratio used to detect the active section while scrolling (0 = top, 1 = bottom). The section closest to that line pulses.

  • dashColordefault "auto"
    string

    Color of the dashes, any CSS color. Auto draws title and subtitle dashes in the foreground color and the rest in a muted gray; a custom color keeps that hierarchy by drawing the minor dashes at 40%.

  • activeColordefault "auto"
    string

    Color of the dash for the section you are reading, any CSS color. Auto leaves it the same color as its neighbours, so only its pulse marks it.

  • dashLengthdefault 110
    number

    Width of the rail in pixels, which is how long a fully swollen dash gets. Every dash length scales with it.

  • gapdefault 8
    number

    Space between dashes in pixels. Each dash also owns the band up to its neighbours, so clicks between dashes land on the closest one.

  • thicknessdefault 1
    number

    Height of each dash in pixels.

  • reachdefault 40
    number

    How far in pixels the pointer's pull reaches: dashes within this distance above or below it swell, the nearest the most.

  • insetdefault 32
    number

    Horizontal space in pixels between the rail and the edges of the nav.

  • updateHashdefault true
    boolean

    Writes #section-id to the URL (history.replaceState) when a dash is picked. Turn it off for embedded readers and modals.

  • labeldefault "Page sections"
    string

    Accessible name of the navigation landmark.

  • dashLabeldefault "Go to {label}"
    string

    Accessible name of each dash; {label} fills in the section label, e.g. "Mergi la {label}".

  • classNamedefault ""
    string

    Additional classes for the outer nav wrapper (data-slot="proximity-sidebar"), which fills its parent's height and centers the stack. Other nav attributes are passed through too.

Parts

  • ProximitySection

    type

    One entry of sections: { id, label, kind?, level? }. id is the element id to scroll to; kind (title, subtitle, section, body) or level (1 to 6) sets the dash length, and without either it is read from the section's first heading.

Examples

Release notes on the right

side right with an orange active dash; scroll the notes and the current version pulses.

Compact rail for a help article

Short dashes, a tighter gap, thicker lines and a smaller reach for a narrow column.

Inside a panel, no URL changes

updateHash off keeps the address bar still, for a rail inside a dialog, a tab or a side panel.

Dependencies

motionmotionui-config

Files

  • components/ui/proximity-sidebar.tsx

FAQ

Is the React Proximity Sidebar component free?

Yes. The source is free and needs no account: copy it from the Code tab or install it with the CLI.

How do I install Proximity Sidebar?

Run npx wingo-ui@latest add proximity-sidebar. It also adds the Wingo UI items it builds on (Motion presets and UIProvider) and the npm package motion. With shadcn, run npx shadcn@latest add https://wingo-ui.com/r/proximity-sidebar.json.

How does Proximity Sidebar behave on phones?

On a phone, touch the rail and slide: the dash under your finger swells, and lifting your finger jumps to it.

See also

Table Of ContentsScroll ProgressSidebaruseScrollStatehookBack To Top

More React navigation components

Bounce SidebarHook SidebarGooey NavUser MenuNav MenuCommand Menu
All 19 React navigation components

Credits

  • •Inspired by devouringdetails.com
PreviousPaginationNext Scroll Progress

Introduction

Proximity Sidebar

A compact document minimap that turns headings and body rhythm into interactive dashes. It stays quiet by default, expands near the pointer, and briefly pulses the matching dash while the reader scrolls.

The sidebar gives readers a sense of document length without forcing another table of contents into the layout. Each dash represents real content, so the minimap feels tied to the page instead of decorative.

Pointer proximity expands nearby dashes continuously. Scroll activation only pulses for a short moment, then returns to baseline so the navigation does not stay visually loud.

The component works best when it mirrors the reading structure: major titles, secondary headings, and smaller descriptive sections all become part of the same quiet stack.

Clicking any dash scrolls to its matching content block. This keeps the minimap useful while preserving the minimal visual language of the original dash-only design.

Titles use the strongest base length, subtitles sit slightly below that, and body sections stay short and muted. The hover expansion uses the same proportions.

The dash animation is driven by transform scale, not layout width. That keeps the interaction responsive even while the scroll container is moving.

Structure

Content Model

The sidebar does not invent marks by itself. It reflects the sections you provide, which makes the visual hierarchy predictable and easy to tune.

A title dash should represent a major region of the document. These are the strongest marks and should appear only where the reader expects a new chapter or major concept.

Subtitle dashes are slightly quieter than title dashes. They are useful for feature groups, installation steps, API categories, or meaningful document turns.

Section dashes sit between markers and body text. They add rhythm without making the entire minimap look like every entry has the same importance.

Body dashes are the smallest marks. They let the sidebar reach the same density as the original implementation while still being connected to real content.

If a section does not provide a kind or level, the component looks for the first heading inside the target element and maps h1, h2, and h3 to visual weight.

Interaction

Behavior

The sidebar balances direct interaction with passive reading. It responds to the pointer, follows scroll position briefly, and stays out of the way when idle.

Hovering over the stack sets a shared pointer value. Each dash measures its own distance from that pointer and scales according to proximity.

When the reader scrolls, the closest content section expands and then settles back once scrolling stops. The active cue is useful but not sticky.

Each dash is rendered as a button with an accessible label. The visual stays minimal, but the control still behaves like real navigation.

The demo uses an internal scroll area. The component detects scrollable parents for the target sections, so the pulse works inside app layouts too.

When reduced motion is requested, clicking a dash jumps without smooth scrolling. The component keeps the navigation predictable for motion-sensitive users.

Selecting a dash updates the URL hash for the target id. Readers can copy or reload the page and keep the same location context.

Visual System

Styling

The dash stack keeps the original visual tone: thin one-pixel marks, eight-pixel spacing, muted small entries, and strong foreground markers.

The original component felt precise because the gaps were tight and consistent. This version keeps the same vertical spacing across all dash types.

The hierarchy comes from length and color instead of heavy stroke widths. That keeps the minimap refined even when the page has many anchors.

Major sections use the foreground color. Smaller items use muted foreground opacity so they create density without overpowering the page.

The strongest mark expands to the same full length as the initial prototype. Smaller entries preserve that same proportional language.

When the sidebar moves to the right side, dashes expand from the right edge. This keeps the motion anchored to the outside rail.

Implementation

Usage

The API stays small. Pass a list of content sections with ids and labels, then place matching ids on the content blocks you want to navigate to.

For a dense docs page, include more than only top-level headings. Add subtitles and meaningful descriptive blocks to create the minimap texture.

Use kind when the visual weight should not depend on the DOM heading level. This is helpful for demos, MDX content, and generated docs.

If your content pipeline already knows heading levels, pass level values from one to six and let the component map them to dash weights.

The component handles the stack and motion. The surrounding page can control sticky positioning, sidebar width, and responsive visibility.

Avoid decorative-only dashes. The minimap is strongest when every line represents something the reader can jump to and understand.

A dense minimap is best for larger reading surfaces. On small screens, it can be hidden or moved behind a compact navigation affordance.

API

Reference

The component accepts a side, optional className, optional active offset, and an ordered list of sections. That order is the order rendered in the dash stack.

Pass sections, then tune side, activeOffset, dashColor, activeColor, dashLength, gap and thickness. The section ids should match elements in the document.

The component renders an accessible navigation rail where each dash is a button tied to its matching content block. On a phone, slide a finger along the rail and lift it to jump.