Segment Group

A segmented control component for selecting one option from a set of mutually exclusive options.

import { SegmentGroup, SegmentGroupItem } from "~/components/segment-group";

export default function SegmentGroupBasicDemo() {
  return (
    <SegmentGroup defaultValue="React">
      <SegmentGroupItem value="React">React</SegmentGroupItem>
      <SegmentGroupItem value="Solid">Solid</SegmentGroupItem>
      <SegmentGroupItem value="Vue">Vue</SegmentGroupItem>
    </SegmentGroup>
  );
}

Installation

CLI

Run the following command to add the component to your project:

npx @ark-preset/cli@latest add segment-group

Manual

Create the recipe file at src/components/recipes/segment-group.ts:

import { tv, type VariantProps } from "tailwind-variants";

export const segmentGroupVariants = tv({
  slots: {
    root: "relative inline-flex items-center p-1 rounded-md bg-muted isolation-inline",
    item: [
      "relative z-10 inline-flex items-center justify-center gap-2 px-3 h-7 rounded-sm cursor-pointer select-none transition-colors duration-150 ease-out",
      "data-[state=checked]:text-foreground data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed",
      "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
    ],
    itemText: "relative z-10 text-sm font-medium",
    itemControl: "hidden",
    indicator:
      "absolute z-0 rounded-sm bg-background shadow-sm w-(--width) h-(--height) left-(--left) transition-[left,top,width,height] ease-out will-change-[left,top,width,height]",
  },
  variants: {
    variant: {
      solid: {
        root: "bg-muted",
        item: "text-muted-foreground data-[state=checked]:text-foreground",
      },
      outline: {
        root: "bg-transparent border border-border",
        item: "text-muted-foreground data-[state=checked]:text-foreground",
        indicator: "border border-border shadow-none will-change-[left,top,width,height]",
      },
    },
    orientation: {
      horizontal: {
        root: "flex-row",
        item: "flex-1 justify-center",
      },
      vertical: {
        root: "flex-col items-stretch h-auto",
        item: "w-full justify-center",
      },
    },
  },
  defaultVariants: {
    variant: "solid",
    orientation: "horizontal",
  },
});

export type SegmentGroupVariants = VariantProps<typeof segmentGroupVariants>;

Create the component directory and files.

src/components/segment-group/segment-group.base.tsx:

import { SegmentGroup as ArkSegmentGroup } from "@ark-ui/solid/segment-group";
import { createContext, useContext, splitProps, type Component } from "solid-js";
import { segmentGroupVariants, labelVariants, type SegmentGroupVariants } from "../recipes/segment-group";

type SegmentGroupVariantContextValue = Pick<SegmentGroupVariants, "variant" | "orientation">;

const SegmentGroupVariantContext = createContext<SegmentGroupVariantContextValue>();

const useSegmentGroupVariant = () => useContext(SegmentGroupVariantContext);

const styles = segmentGroupVariants();

const Root: Component<ArkSegmentGroup.RootProps & SegmentGroupVariants> = (props) => {
  const [local, others] = splitProps(props, ["class", "variant", "orientation"]);
  return (
    <SegmentGroupVariantContext.Provider
      value={{ variant: local.variant, orientation: local.orientation }}
    >
      <ArkSegmentGroup.Root
        class={styles.root({
          class: local.class,
          variant: local.variant,
          orientation: local.orientation,
        })}
        orientation={local.orientation}
        {...others}
      />
    </SegmentGroupVariantContext.Provider>
  );
};

const RootProvider: Component<ArkSegmentGroup.RootProviderProps & SegmentGroupVariants> = (
  props,
) => {
  const [local, others] = splitProps(props, ["class", "variant", "orientation"]);
  return (
    <SegmentGroupVariantContext.Provider
      value={{ variant: local.variant, orientation: local.orientation }}
    >
      <ArkSegmentGroup.RootProvider
        class={styles.root({
          class: local.class,
          variant: local.variant,
          orientation: local.orientation,
        })}
        {...others}
      />
    </SegmentGroupVariantContext.Provider>
  );
};

const Label: Component<ArkSegmentGroup.LabelProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkSegmentGroup.Label class={labelVariants({ class: local.class })} {...others} />;
};

const Item: Component<ArkSegmentGroup.ItemProps & SegmentGroupVariants> = (props) => {
  const ctx = useSegmentGroupVariant();
  const [local, others] = splitProps(props, ["class", "variant", "orientation"]);
  return (
    <ArkSegmentGroup.Item
      class={styles.item({
        class: local.class,
        variant: local.variant ?? ctx?.variant,
        orientation: local.orientation ?? ctx?.orientation,
      })}
      {...others}
    />
  );
};

const ItemText: Component<ArkSegmentGroup.ItemTextProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkSegmentGroup.ItemText class={styles.itemText({ class: local.class })} {...others} />;
};

const ItemControl: Component<ArkSegmentGroup.ItemControlProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return (
    <ArkSegmentGroup.ItemControl class={styles.itemControl({ class: local.class })} {...others} />
  );
};

const ItemHiddenInput = ArkSegmentGroup.ItemHiddenInput;

const Indicator: Component<ArkSegmentGroup.IndicatorProps & SegmentGroupVariants> = (props) => {
  const ctx = useSegmentGroupVariant();
  const [local, others] = splitProps(props, ["class", "variant", "orientation"]);
  return (
    <ArkSegmentGroup.Indicator
      class={styles.indicator({
        class: local.class,
        variant: local.variant ?? ctx?.variant,
        orientation: local.orientation ?? ctx?.orientation,
      })}
      {...others}
    />
  );
};

export const SegmentGroup = {
  Root,
  RootProvider,
  Label,
  Item,
  ItemText,
  ItemControl,
  ItemHiddenInput,
  Indicator,
};

export { SegmentGroupVariantContext, useSegmentGroupVariant };

src/components/segment-group/index.tsx:

import { splitProps, type Component } from "solid-js";
import { SegmentGroup as SegmentGroupBase } from "./segment-group.base";
import { SegmentGroup as ArkSegmentGroup } from "@ark-ui/solid/segment-group";
import type { SegmentGroupVariants } from "../recipes/segment-group";

const SegmentGroup: Component<ArkSegmentGroup.RootProps & SegmentGroupVariants> = (props) => {
  const [local, others] = splitProps(props, ["variant", "orientation", "children"]);
  return (
    <SegmentGroupBase.Root variant={local.variant} orientation={local.orientation} {...others}>
      <SegmentGroupBase.Indicator />
      {local.children}
    </SegmentGroupBase.Root>
  );
};

const SegmentGroupItem: Component<ArkSegmentGroup.ItemProps & SegmentGroupVariants> = (props) => {
  const [local, others] = splitProps(props, ["children"]);
  return (
    <SegmentGroupBase.Item {...others}>
      <SegmentGroupBase.ItemText>{local.children}</SegmentGroupBase.ItemText>
      <SegmentGroupBase.ItemControl />
      <SegmentGroupBase.ItemHiddenInput />
    </SegmentGroupBase.Item>
  );
};

export { SegmentGroup, SegmentGroupItem, SegmentGroupBase };

export { segmentGroupVariants, type SegmentGroupVariants } from "../recipes/segment-group";

Note: Make sure your project has the Tailwind CSS theme variables set up (--background, --foreground, --ring, --border, etc.) or override the utility classes to match your design system.

Usage

Basic Usage

import { SegmentGroup, SegmentGroupItem } from "~/components/segment-group";

export default function SegmentGroupBasicDemo() {
  return (
    <SegmentGroup defaultValue="React">
      <SegmentGroupItem value="React">React</SegmentGroupItem>
      <SegmentGroupItem value="Solid">Solid</SegmentGroupItem>
      <SegmentGroupItem value="Vue">Vue</SegmentGroupItem>
    </SegmentGroup>
  );
}

Vertical

Set the orientation prop to “vertical” for a vertical layout.

import { SegmentGroup, SegmentGroupItem } from "~/components/segment-group";

export default function SegmentGroupVerticalDemo() {
  return (
    <SegmentGroup defaultValue="Svelte" orientation="vertical">
      <SegmentGroupItem value="Svelte">Svelte</SegmentGroupItem>
      <SegmentGroupItem value="Vue">Vue</SegmentGroupItem>
    </SegmentGroup>
  );
}

Controlled Value

Use value and onValueChange to control the selection state externally:

Selected: Solid

import { Index, createSignal } from "solid-js";
import { SegmentGroup, SegmentGroupItem } from "~/components/segment-group";

const frameworks = ["React", "Solid", "Vue"];

export default function SegmentGroupControlledDemo() {
  const [value, setValue] = createSignal("Solid");

  return (
    <div class="space-y-4">
      <p class="text-sm text-muted-foreground">Selected: {value()}</p>
      <SegmentGroup value={value()} onValueChange={(e: any) => setValue(e.value || "Solid")}>
        <Index each={frameworks}>
          {(framework) => <SegmentGroupItem value={framework()}>{framework()}</SegmentGroupItem>}
        </Index>
      </SegmentGroup>
    </div>
  );
}

Disabled Item

Individual items can be disabled using the disabled prop:

import { Index } from "solid-js";
import { SegmentGroup, SegmentGroupItem } from "~/components/segment-group";

const frameworks = ["React", "Solid", "Vue"];

export default function SegmentGroupDisabledDemo() {
  return (
    <div>
      <SegmentGroup defaultValue="React">
        <Index each={frameworks}>
          {(framework) => (
            <SegmentGroupItem value={framework()} disabled={framework() === "Vue"}>
              {framework()}
            </SegmentGroupItem>
          )}
        </Index>
      </SegmentGroup>
    </div>
  );
}

Outline Variant

Use {variant=“outline”} for a bordered style:

import { Index } from "solid-js";
import { SegmentGroup, SegmentGroupItem } from "~/components/segment-group";

const frameworks = ["React", "Solid", "Vue"];

export default function SegmentGroupOutlineDemo() {
  return (
    <div>
      <SegmentGroup defaultValue="React" variant="outline">
        <Index each={frameworks}>
          {(framework) => <SegmentGroupItem value={framework()}>{framework()}</SegmentGroupItem>}
        </Index>
      </SegmentGroup>
    </div>
  );
}

Advanced Usage

When the composite SegmentGroup doesn’t provide enough control, import the raw primitive parts from the base file directly:

import { SegmentGroup, useSegmentGroupVariant } from "~/components/segment-group/segment-group.base";

Or import SegmentGroupBase (the raw parts namespace) from the composite entry point:

import { SegmentGroupBase } from "~/components/segment-group";

RootProvider Pattern

For full control over the segment group machine, use useSegmentGroup with SegmentGroupBase.RootProvider. This allows reading the internal state (e.g., selected value) outside the root:

Selected: React

import { Index, createMemo } from "solid-js";
import { SegmentGroupBase } from "~/components/segment-group";
import { useSegmentGroup } from "@ark-ui/solid/segment-group";

const frameworks = ["React", "Solid", "Vue"];

export default function SegmentGroupRootProviderDemo() {
  const segmentGroup = useSegmentGroup({ defaultValue: "React" });
  const value = createMemo(() => segmentGroup().value);

  return (
    <div class="space-y-4">
      <p class="text-sm text-muted-foreground">Selected: {value()}</p>
      <SegmentGroupBase.RootProvider value={segmentGroup}>
        <SegmentGroupBase.Indicator />
        <Index each={frameworks}>
          {(framework) => (
            <SegmentGroupBase.Item value={framework()}>
              <SegmentGroupBase.ItemText>{framework()}</SegmentGroupBase.ItemText>
              <SegmentGroupBase.ItemControl />
              <SegmentGroupBase.ItemHiddenInput />
            </SegmentGroupBase.Item>
          )}
        </Index>
      </SegmentGroupBase.RootProvider>
    </div>
  );
}

API Reference

See the Ark UI SegmentGroup documentation.