npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mshafiqyajid/react-segmented-control

v0.3.1

Published

Headless segmented-control hook and styled component for React. Buttery sliding indicator, full keyboard nav, themable, SSR-safe, fully typed.

Readme

@mshafiqyajid/react-segmented-control

Full docs →

A polished iOS-style segmented control for React — with a buttery sliding indicator that follows the active segment. Comes in two flavors:

  • Styled<SegmentedControlStyled> with variants (solid / pill / underline), sizes, tones, full a11y.
  • HeadlessuseSegmentedControl() hook + unstyled <SegmentedControl> primitive. Bring your own UI.

Generic over option type (works with strings, numbers, or full objects). Full keyboard nav. Zero dependencies. SSR-safe. Fully typed. ESM + CJS.

Install

npm install @mshafiqyajid/react-segmented-control

Peer dependency: react >= 17.

Quick start

Styled (recommended)

import { SegmentedControlStyled } from "@mshafiqyajid/react-segmented-control/styled";
import "@mshafiqyajid/react-segmented-control/styles.css";

export function ViewSwitcher() {
  const [view, setView] = useState("week");
  return (
    <SegmentedControlStyled
      options={["day", "week", "month"]}
      value={view}
      onChange={setView}
      tone="primary"
    />
  );
}

Headless

import { SegmentedControl } from "@mshafiqyajid/react-segmented-control";

<SegmentedControl
  options={["Code", "Preview", "Tests"]}
  defaultValue="Preview"
  onChange={(v) => console.log(v)}
/>

Hook (full custom UI)

import { useSegmentedControl } from "@mshafiqyajid/react-segmented-control";

function CustomTabs() {
  const { options, rootProps, indicatorStyle } = useSegmentedControl({
    options: ["Code", "Preview", "Tests"],
  });

  return (
    <div {...rootProps} style={indicatorStyle} className="my-tabs">
      <span className="my-indicator" /> {/* uses --rsc-indicator-x / --rsc-indicator-width */}
      {options.map((o) => (
        <button key={o.index} {...o.buttonProps}>
          {o.label}
        </button>
      ))}
    </div>
  );
}

Recipes

Variants

<SegmentedControlStyled options={["A", "B", "C"]} variant="solid" />     {/* default */}
<SegmentedControlStyled options={["A", "B", "C"]} variant="pill" />
<SegmentedControlStyled options={["A", "B", "C"]} variant="underline" />

Sizes and tones

<SegmentedControlStyled options={["A","B"]} size="sm" tone="success" />
<SegmentedControlStyled options={["A","B"]} size="md" tone="primary" />
<SegmentedControlStyled options={["A","B"]} size="lg" tone="danger"  />

Full width

Stretches to its parent and distributes segments evenly.

<SegmentedControlStyled options={["Newest", "Top", "Following"]} fullWidth />

Disabled options

<SegmentedControlStyled
  options={[
    "Free",
    "Pro",
    { value: "Enterprise", disabled: true },
  ]}
/>

Disabled options are skipped by arrow keys and ignore clicks.

Object options with custom labels

<SegmentedControlStyled
  options={[
    { value: "asc",  label: "↑ Ascending" },
    { value: "desc", label: "↓ Descending" },
  ]}
/>

Generic over any value type

type Sort = { field: "name" | "date"; dir: "asc" | "desc" };

<SegmentedControlStyled<Sort>
  options={[
    { value: { field: "name", dir: "asc" }, label: "Name ↑" },
    { value: { field: "date", dir: "desc" }, label: "Date ↓" },
  ]}
  defaultValue={{ field: "name", dir: "asc" }}
  equals={(a, b) => a.field === b.field && a.dir === b.dir}
/>

Theme via CSS variables

.brand-segmented {
  --rsc-bg-track: #fef3c7;
  --rsc-bg-indicator: #f59e0b;
  --rsc-fg-active: #ffffff;
  --rsc-radius: 14px;
}
<SegmentedControlStyled options={["A", "B", "C"]} className="brand-segmented" />

Force a theme without prefers-color-scheme:

<div data-rsc-theme="dark">
  <SegmentedControlStyled options={["A", "B", "C"]} />
</div>

API

<SegmentedControlStyled>

| Prop | Type | Default | Description | | -------------- | ------------------------------------------------- | ----------- | ------------------------------------------------------------------------ | | options | Array<T \| { value: T; label?; disabled? }> | — | Required. Strings/numbers auto-wrapped; pass objects for labels/disabled. | | value | T | — | Controlled value. | | defaultValue | T | first opt | Uncontrolled initial value. | | onChange | (value: T) => void | — | Fires when the active value changes. | | disabled | boolean | false | Disable the entire control. | | equals | (a: T, b: T) => boolean | Object.is | Custom equality for object/complex values. | | variant | "solid" \| "pill" \| "underline" | "solid" | Visual style. | | size | "sm" \| "md" \| "lg" | "md" | Size. | | tone | "neutral" \| "primary" \| "success" \| "danger" | "primary" | Color theme. | | fullWidth | boolean | false | Stretch to container width. | | label | ReactNode | — | Label rendered above. | | hint | ReactNode | — | Helper text below. |

useSegmentedControl(options)

Returns { value, options, rootProps, indicatorStyle, setValue }:

  • options[i].buttonProps — spread onto your <button> to wire it up.
  • rootProps — spread onto the wrapper for role="radiogroup".
  • indicatorStyle — apply to the wrapper. Sets CSS vars --rsc-indicator-x, --rsc-indicator-width, and --rsc-indicator-ready (0/1).

<SegmentedControl> (headless primitive)

Same options as the hook, plus:

  • renderOption?: (state) => ReactNode — replace the default button per option.
  • showIndicator?: boolean — toggle the .rsc-indicator span (default true).
  • optionProps?: ... — extra props applied to every default-rendered button.
  • children?: ({ options, value, setValue, indicatorStyle }) => ReactNode — full custom render.

CSS variables

Override on .rsc-root, on the track via className, or on :root:

| Variable | Default | Description | | ------------------------- | ------------- | --------------------------------- | | --rsc-bg-track | #f4f4f5 | Track background | | --rsc-bg-indicator | #ffffff | Indicator (sliding pill) color | | --rsc-fg-active | #18181b | Active segment label color | | --rsc-fg-inactive | #52525b | Inactive segment label color | | --rsc-shadow-indicator | subtle | Indicator drop shadow | | --rsc-ring | indigo glow | Focus ring | | --rsc-radius | 9px | Track corner radius | | --rsc-radius-inner | 7px | Indicator/segment corner radius | | --rsc-padding | 3px | Track inner padding | | --rsc-segment-padding-y | 0.4rem | Vertical segment padding | | --rsc-segment-padding-x | 0.85rem | Horizontal segment padding | | --rsc-font-size | 0.85rem | Segment font size | | --rsc-duration | 320ms | Indicator slide duration | | --rsc-ease-spring | spring curve | Indicator slide easing |

The styled component automatically:

  • Switches palette under prefers-color-scheme: dark
  • Disables animations under prefers-reduced-motion: reduce
  • Uses ResizeObserver to keep the indicator aligned when fonts/layout change
  • Forwards ref to the track <div>

Browser support

Modern evergreen browsers. Uses ResizeObserver (universally supported) for indicator measurement. SSR-safe — falls back to useEffect on the server.

License

MIT © Shafiq Yajid

Form integration

<form>
  <SegmentedControlStyled
    name="plan"
    label="Plan"
    options={["free", "pro", "team"]}
    defaultValue="pro"
    required
    error={errors.plan}
  />
</form>

| Prop | Type | Description | |---|---|---| | name | string | Renders a hidden input carrying String(value) for native form submission | | id | string | Wrapper id used for label association | | required | boolean | Surfaces aria-required and required on the hidden input | | error | ReactNode | Inline error text. Flips tone to danger and lands data-invalid="true" | | invalid | boolean | Force the invalid state without inline error text | | label / hint | ReactNode | Already supported |

The wrapper lands data-invalid="true" whenever error or invalid is truthy.

What's new in 0.3.0

  • Per-segment badge — option config accepts { value, label, badge }; badge renders as a pill on the right of the label, with active-state styling.
  • scrollable: true — track scrolls horizontally instead of wrapping when content overflows.
  • equalize: true — every segment is sized to the widest via CSS grid (grid-template-columns: repeat(N, 1fr)).
  • href per option — when set, segment renders as <a href> for routing while still wired to the control's keyboard nav.
  • The hook return shape exposes the new badge and href so headless consumers see them too.