Components
Button Group
Visually groups adjacent buttons into one control. Purely presentational.
Installation
npx kosmesis add button-grouppnpm dlx kosmesis add button-groupyarn dlx kosmesis add button-groupbunx kosmesis add button-groupCopy and paste the following code into your project.
import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { cn } from "@/lib/utils";export interface ButtonGroupProps { orientation?: "horizontal" | "vertical"; class?: string; id?: string; children?: Children;}/** Purely presentational — no Morphos equivalent, same as upstream shadcn/ui. */@Component()export class ButtonGroup extends StatelessComponent<ButtonGroupProps> { render() { const { orientation = "horizontal", class: cls, id, children } = this.props; return ( <div id={id} role="group" data-orientation={orientation} class={cn( "flex w-fit items-stretch", "[&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md", "[&>*:not(:first-child)]:-ml-px", "data-[orientation=vertical]:flex-col", "data-[orientation=vertical]:[&>*]:rounded-none data-[orientation=vertical]:[&>*:first-child]:rounded-t-md data-[orientation=vertical]:[&>*:last-child]:rounded-b-md", "data-[orientation=vertical]:[&>*:not(:first-child)]:mt-[-1px] data-[orientation=vertical]:[&>*:not(:first-child)]:ml-0", cls, )} > {children} </div> ); }}export interface ButtonGroupSeparatorProps { class?: string;}@Component()export class ButtonGroupSeparator extends StatelessComponent<ButtonGroupSeparatorProps> { render() { const { class: cls } = this.props; return ( <div role="separator" class={cn( "w-px self-stretch bg-border in-data-[orientation=vertical]:h-px in-data-[orientation=vertical]:w-auto in-data-[orientation=vertical]:self-auto", cls, )} /> ); }}export interface ButtonGroupTextProps { class?: string; children?: Children;}@Component()export class ButtonGroupText extends StatelessComponent<ButtonGroupTextProps> { render() { const { class: cls, children } = this.props; return ( <div class={cn( "flex items-center gap-2 rounded-md border bg-muted px-4 text-sm font-medium", cls, )} > {children} </div> ); }}Install the following dependencies:
npm install @praxisjs/csspnpm add @praxisjs/cssyarn add @praxisjs/cssbun add @praxisjs/cssCopy and paste the following code into your project.
import { StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);class ButtonGroupStyles extends Stylesheet { // `& > *[class]` (not plain `& > *`): every real child here (`Button`, `ButtonGroupText`, // `ButtonGroupSeparator`) sets its own `border-radius` at the same specificity as a bare // descendant-class selector (one class each). Without the `[class]` attribute bump, this reset // ties with the child's own rule and the winner is decided by `@Styled` injection order — which // component class happens to be defined first in its file — instead of by which rule is actually // meant to win. The attribute selector adds specificity so the group's reset always wins. $root = this.css({ display: "flex", width: "fit-content", alignItems: "stretch" }) .on("& > *[class]", { borderRadius: "0" }) .on("& > *:first-child", { borderTopLeftRadius: "0.375rem", borderBottomLeftRadius: "0.375rem" }) .on("& > *:last-child", { borderTopRightRadius: "0.375rem", borderBottomRightRadius: "0.375rem" }) .on("& > *:not(:first-child)", { marginLeft: "-1px" }) .on('&[data-orientation="vertical"]', { flexDirection: "column" }) .on('&[data-orientation="vertical"] > *[class]', { borderRadius: "0" }) .on('&[data-orientation="vertical"] > *:first-child', { borderTopLeftRadius: "0.375rem", borderTopRightRadius: "0.375rem", borderBottomLeftRadius: "0", }) .on('&[data-orientation="vertical"] > *:last-child', { borderBottomLeftRadius: "0.375rem", borderBottomRightRadius: "0.375rem", borderTopRightRadius: "0", }) .on('&[data-orientation="vertical"] > *:not(:first-child)', { marginLeft: "0", marginTop: "-1px" }); $separator = this.css({ width: "1px", alignSelf: "stretch", backgroundColor: t.border }).on( '[data-orientation="vertical"] &', { height: "1px", width: "auto", alignSelf: "auto" }, ); $text = this.css({ display: "flex", alignItems: "center", gap: "0.5rem", borderRadius: `calc(${t.radius} - 2px)`, border: `1px solid ${t.border}`, backgroundColor: t.muted, padding: "0 1rem", fontSize: "0.875rem", fontWeight: 500, });}export interface ButtonGroupProps { orientation?: "horizontal" | "vertical"; class?: string; id?: string; children?: Children;}/** Purely presentational — no Morphos equivalent, same as upstream shadcn/ui. */@Component()export class ButtonGroup extends StatelessComponent<ButtonGroupProps> { @Styled(ButtonGroupStyles) $s!: ButtonGroupStyles; render() { const { orientation = "horizontal", class: cls, id, children } = this.props; return ( <div id={id} role="group" data-orientation={orientation} class={cx(this.$s.$root, cls)}> {children} </div> ); }}export interface ButtonGroupSeparatorProps { class?: string;}@Component()export class ButtonGroupSeparator extends StatelessComponent<ButtonGroupSeparatorProps> { @Styled(ButtonGroupStyles) $s!: ButtonGroupStyles; render() { const { class: cls } = this.props; return <div role="separator" class={cx(this.$s.$separator, cls)} />; }}export interface ButtonGroupTextProps { class?: string; children?: Children;}@Component()export class ButtonGroupText extends StatelessComponent<ButtonGroupTextProps> { @Styled(ButtonGroupStyles) $s!: ButtonGroupStyles; render() { const { class: cls, children } = this.props; return <div class={cx(this.$s.$text, cls)}>{children}</div>; }}Examples
Usage
import { Button } from "@/components/ui/button";
import { ButtonGroup, ButtonGroupSeparator, ButtonGroupText } from "@/components/ui/button-group";
<ButtonGroup>
<Button variant="outline">Copy</Button>
<Button variant="outline">Paste</Button>
<Button variant="outline">Cut</Button>
</ButtonGroup>
<ButtonGroup>
<ButtonGroupText>https://</ButtonGroupText>
<Button variant="outline">example.com</Button>
</ButtonGroup>
<ButtonGroup orientation="vertical">
<Button variant="outline">Up</Button>
<ButtonGroupSeparator />
<Button variant="outline">Down</Button>
</ButtonGroup>