VayuUI

Affix

Pins content to the viewport or a scroll container when scrolling past it.

Installation

npx vayu-ui init #one time only
npx vayu-ui add affix
npx vayu-ui add -t affix #with test case needs

Usage

1. Top Affix (with Offset)

This bar sticks to the top of the viewport with a 20px offset. It uses zIndex: 50.

Scroll Area (300px)

2. Bottom Affix

This bar sticks to the bottom of the viewport. Useful for cookie consents or action bars.

Scroll Area (300px)

Sticks to the bottom edge.

3. Custom Target Container

The Affix can be scoped to a specific scrollable element, not just the window. Scroll inside the box below.

Start scrolling down inside this box...

Scroll Area (200px)

Scroll Area (200px)

I am stuck to the bottom of the custom container!

Scroll Area (300px)

Scroll Area (200px)

4. Accessibility & Custom Styles

This example passes a custom className and style. It also uses standard aria-label for screen readers.

Custom Styled Banner (Rounded via style prop!)

Scroll Area (300px)

<Affix offset={20} zIndex={50} role="navigation" aria-label="Sticky Top Navigation">
  <div className="bg-brand text-brand-content p-4 rounded-control shadow-elevated flex justify-between items-center">
    <span className="font-bold">Top Sticky Bar</span>
    <span className="text-xs bg-brand/80 px-2 py-1 rounded-control">Status: Affixed</span>
  </div>
</Affix>

<Affix position="bottom" offset={0} zIndex={40}>
  <div className="bg-elevated text-elevated-content p-4 rounded-control shadow-elevated flex justify-between items-center">
    <span className="font-bold">Bottom Action Bar</span>
    <button className="bg-success text-success-content px-4 py-2 rounded-control text-sm font-medium">Save Changes</button>
  </div>
</Affix>

<div ref={scrollRef} className="h-100 overflow-auto border-2 border-border rounded-surface relative">
  <div className="p-4">
    <Affix target={scrollRef} position="bottom" offset={10}>
      <div className="bg-warning text-warning-content p-3 rounded-control shadow-elevated text-center font-medium">
        Scoped to this container
      </div>
    </Affix>
  </div>
</div>

Anatomy

import { Affix } from 'vayu-ui';

<Affix offset={20} position="top" role="navigation" aria-label="Sticky navigation">
  {/* Content to be affixed */}
</Affix>;
  • Affix — Root container. Pins its child content to the viewport or a custom scroll container when the scroll threshold is reached.
  • Placeholder (internal) — Maintains layout height when the affixed element switches to position: fixed. Marked aria-hidden="true".
  • Affixed element (internal) — The visible wrapper that receives data-affixed and dynamic position styles.

Accessibility

  • Keyboard Support:
    • The affixed content is not focus-trapped. Users can tab through it normally as part of the document flow.
    • All interactive children remain fully keyboard accessible when affixed.
  • ARIA Attributes:
    • Pass role and aria-label to provide semantic context to screen readers.
    • data-affixed attribute is set on the element when content is affixed, useful for CSS hooks and testing.
    • The placeholder element uses aria-hidden="true" to prevent duplicate announcements.
  • Focus Behavior:
    • Focus is not affected by the affix state change.
    • Interactive children (buttons, links) remain focusable in both static and affixed states.

Screen reader behavior

When the affix element transitions between static and fixed positioning, no live region announcement is triggered. Screen readers perceive the content as a standard <div> with whatever role and aria-label the developer provides. The placeholder element is hidden from the accessibility tree via aria-hidden="true", so users will not encounter duplicate content. If onAffixed is used to toggle visual indicators, those changes are not announced unless wrapped in a live region (aria-live).

Component Folder Structure

Affix/
├── Affix.tsx        # Root component with ref management and composition
├── hooks.ts         # useAffixMeasure and useAffixScroll hooks for scroll logic
├── types.ts         # AffixPosition and AffixProps type definitions
├── index.ts         # Re-exports component and types
└── README.md        # Component usage reference

Props

Affix

PropTypeDefaultDescription
offsetnumber0Distance in pixels from the viewport or container edge.
position"top" | "bottom""top"Which edge to pin to.
targetHTMLElement | nullnullCustom scroll container. Defaults to window.
zIndexnumber100Stack order when affixed.
onAffixed(affixed: boolean) => voidCallback fired when affixed state changes.
childrenReactNodeContent to be affixed.
classNamestringAdditional CSS classes.
styleCSSPropertiesInline styles merged with affix positioning styles.

Inherits all standard HTML <div> attributes (e.g., role, aria-label, id).

On this page