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-groupManual
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.