WingoUI
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime Picker
Forms
Address InputCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text Editor
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSkeletonSpinnerToast
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 chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselImage CropperImage LightboxLocation MapQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Gravity Letters
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
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowPost Listing WizardPricing PageRegister PageSettings PageUsers Admin Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryThemeCredits

Navigation

Proximity Sidebar

NavigationProximity Sidebar
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 />
FreeRare UI

v1.0.0·Added in 1.0.0

A document minimap of thin dashes that swell near the pointer and pulse with the section you are reading.

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.

Add to a project

npx wingo-ui@latest add proximity-sidebar

Dependencies

motionmotionui-config

Files

  • components/ui/proximity-sidebar.tsx
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.