Button Groups
Flexible button group components for displaying multiple buttons together. Supports various layouts, sizes, and customization options. previews<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-transparent text-slate-900 dark:text-white hover:text-slate-700 dark:hover:text-slate-200 border-slate-300 dark:border-slate-600 hover:border-slate-400 dark:hover:border-slate-500 border px-3 py-2 text-sm rounded-md">
Button 1
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-transparent text-slate-900 dark:text-white hover:text-slate-700 dark:hover:text-slate-200 border-slate-300 dark:border-slate-600 hover:border-slate-400 dark:hover:border-slate-500 border px-3 py-2 text-sm rounded-md">
Button 2
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-transparent text-slate-900 dark:text-white hover:text-slate-700 dark:hover:text-slate-200 border-slate-300 dark:border-slate-600 hover:border-slate-400 dark:hover:border-slate-500 border px-3 py-2 text-sm rounded-md">
Button 3
</button>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup>
<Button variant="outline" color="white">Button 1</Button>
<Button variant="outline" color="white">Button 2</Button>
<Button variant="outline" color="white">Button 3</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-gray-500 hover:bg-gray-600 dark:bg-gray-600 dark:hover:bg-gray-500 text-white dark:text-white hover:text-gray-100 dark:hover:text-gray-200 px-3 py-2 text-sm rounded-md">
Button 1
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-gray-500 hover:bg-gray-600 dark:bg-gray-600 dark:hover:bg-gray-500 text-white dark:text-white hover:text-gray-100 dark:hover:text-gray-200 px-3 py-2 text-sm rounded-md">
Button 2
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-gray-500 hover:bg-gray-600 dark:bg-gray-600 dark:hover:bg-gray-500 text-white dark:text-white hover:text-gray-100 dark:hover:text-gray-200 px-3 py-2 text-sm rounded-md">
Button 3
</button>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup showBorder={false}>
<Button variant="solid" color="gray">Button 1</Button>
<Button variant="solid" color="gray">Button 2</Button>
<Button variant="solid" color="gray">Button 3</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center divide-x divide-gray-300 dark:divide-gray-600 rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-gray-500 hover:bg-gray-600 dark:bg-gray-600 dark:hover:bg-gray-500 text-white dark:text-white hover:text-gray-100 dark:hover:text-gray-200 px-3 py-2 text-sm rounded-md">
Button 1
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-gray-500 hover:bg-gray-600 dark:bg-gray-600 dark:hover:bg-gray-500 text-white dark:text-white hover:text-gray-100 dark:hover:text-gray-200 px-3 py-2 text-sm rounded-md">
Button 2
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-gray-500 hover:bg-gray-600 dark:bg-gray-600 dark:hover:bg-gray-500 text-white dark:text-white hover:text-gray-100 dark:hover:text-gray-200 px-3 py-2 text-sm rounded-md">
Button 3
</button>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup withDividers={true}>
<Button variant="solid" color="gray">Button 1</Button>
<Button variant="solid" color="gray">Button 2</Button>
<Button variant="solid" color="gray">Button 3</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
<div
class="inline-flex overflow-hidden flex-col [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-t-md [&>*:last-child]:rounded-b-md [&>*:not(:first-child)]:-mt-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 1
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 2
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 3
</button>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup orientation="vertical">
<Button variant="solid" color="blue">Button 1</Button>
<Button variant="solid" color="blue">Button 2</Button>
<Button variant="solid" color="blue">Button 3</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
<div
class="inline-flex overflow-hidden flex-col [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center divide-y divide-gray-300 dark:divide-gray-600 rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-t-md [&>*:last-child]:rounded-b-md [&>*:not(:first-child)]:-mt-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 1
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 2
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 3
</button>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup orientation="vertical" withDividers={true}>
<Button variant="solid" color="blue">Button 1</Button>
<Button variant="solid" color="blue">Button 2</Button>
<Button variant="solid" color="blue">Button 3</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
<div class="space-y-4 grid grid-cols-1">
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-2 [&>*]:py-1 [&>*]:text-xs [&>*]:justify-center divide-x divide-gray-300 dark:divide-gray-600 rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
XS
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
XS
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
XS
</button>
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-2 [&>*]:py-1 [&>*]:text-sm [&>*]:justify-center divide-x divide-gray-300 dark:divide-gray-600 rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
SM
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
SM
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
SM
</button>
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center divide-x divide-gray-300 dark:divide-gray-600 rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
MD
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
MD
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
MD
</button>
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-4 [&>*]:py-2 [&>*]:text-base [&>*]:justify-center divide-x divide-gray-300 dark:divide-gray-600 rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
LG
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
LG
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
LG
</button>
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-5 [&>*]:py-3 [&>*]:text-lg [&>*]:justify-center divide-x divide-gray-300 dark:divide-gray-600 rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
XL
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
XL
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
XL
</button>
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-6 [&>*]:py-4 [&>*]:text-xl [&>*]:justify-center divide-x divide-gray-300 dark:divide-gray-600 rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
2XL
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
2XL
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
2XL
</button>
</div>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup size="xs" withDividers={true}>
<Button variant="solid" color="blue">XS</Button>
<Button variant="solid" color="blue">XS</Button>
<Button variant="solid" color="blue">XS</Button>
</ButtonGroup>
<ButtonGroup size="sm" withDividers={true}>
<Button variant="solid" color="blue">SM</Button>
<Button variant="solid" color="blue">SM</Button>
<Button variant="solid" color="blue">SM</Button>
</ButtonGroup>
<ButtonGroup size="md" withDividers={true}>
<Button variant="solid" color="blue">MD</Button>
<Button variant="solid" color="blue">MD</Button>
<Button variant="solid" color="blue">MD</Button>
</ButtonGroup>
<ButtonGroup size="lg" withDividers={true}>
<Button variant="solid" color="blue">LG</Button>
<Button variant="solid" color="blue">LG</Button>
<Button variant="solid" color="blue">LG</Button>
</ButtonGroup>
<ButtonGroup size="xl" withDividers={true}>
<Button variant="solid" color="blue">XL</Button>
<Button variant="solid" color="blue">XL</Button>
<Button variant="solid" color="blue">XL</Button>
</ButtonGroup>
<ButtonGroup size="2xl" withDividers={true}>
<Button variant="solid" color="blue">2XL</Button>
<Button variant="solid" color="blue">2XL</Button>
<Button variant="solid" color="blue">2XL</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
<div class="space-y-4 grid grid-cols-1">
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center divide-x divide-gray-300 dark:divide-gray-600 rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 1
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 2
</button>
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center divide-x divide-gray-300 dark:divide-gray-600 rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 1
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 2
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 3
</button>
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center divide-x divide-gray-300 dark:divide-gray-600 rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 1
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 2
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 3
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 4
</button>
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center divide-x divide-gray-300 dark:divide-gray-600 rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 1
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 2
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 3
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 4
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 5
</button>
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center divide-x divide-gray-300 dark:divide-gray-600 rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 1
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 2
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 3
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 4
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 5
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 6
</button>
</div>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup items={2} withDividers={true}>
<Button variant="solid" color="blue">Button 1</Button>
<Button variant="solid" color="blue">Button 2</Button>
</ButtonGroup>
<ButtonGroup items={3} withDividers={true}>
<Button variant="solid" color="blue">Button 1</Button>
<Button variant="solid" color="blue">Button 2</Button>
<Button variant="solid" color="blue">Button 3</Button>
</ButtonGroup>
<ButtonGroup items={4} withDividers={true}>
<Button variant="solid" color="blue">Button 1</Button>
<Button variant="solid" color="blue">Button 2</Button>
<Button variant="solid" color="blue">Button 3</Button>
<Button variant="solid" color="blue">Button 4</Button>
</ButtonGroup>
<ButtonGroup items={5} withDividers={true}>
<Button variant="solid" color="blue">Button 1</Button>
<Button variant="solid" color="blue">Button 2</Button>
<Button variant="solid" color="blue">Button 3</Button>
<Button variant="solid" color="blue">Button 4</Button>
<Button variant="solid" color="blue">Button 5</Button>
</ButtonGroup>
<ButtonGroup items={6} withDividers={true}>
<Button variant="solid" color="blue">Button 1</Button>
<Button variant="solid" color="blue">Button 2</Button>
<Button variant="solid" color="blue">Button 3</Button>
<Button variant="solid" color="blue">Button 4</Button>
<Button variant="solid" color="blue">Button 5</Button>
<Button variant="solid" color="blue">Button 6</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
<div
class="inline-flex overflow-hidden flex-col [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-t-md [&>*:last-child]:rounded-b-md [&>*:not(:first-child)]:-mt-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 1
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 2
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 3
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 4
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 5
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Button 6
</button>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup orientation="vertical">
<Button variant="solid" color="blue">Button 1</Button>
<Button variant="solid" color="blue">Button 2</Button>
<Button variant="solid" color="blue">Button 3</Button>
<Button variant="solid" color="blue">Button 4</Button>
<Button variant="solid" color="blue">Button 5</Button>
<Button variant="solid" color="blue">Button 6</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:inline-flex [&>*]:items-center [&>*]:justify-center rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
<span class="inline-block size-5 w-5 h-5 mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
fill-rule="evenodd"
d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z"
clip-rule="evenodd"></path>
</svg>
</span>
User
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
<span class="inline-block size-5 w-5 h-5 mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
d="M17.004 10.407c.138.435-.216.842-.672.842h-3.465a.75.75 0 0 1-.65-.375l-1.732-3c-.229-.396-.053-.907.393-1.004a5.252 5.252 0 0 1 6.126 3.537ZM8.12 8.464c.307-.338.838-.235 1.066.16l1.732 3a.75.75 0 0 1 0 .75l-1.732 3c-.229.397-.76.5-1.067.161A5.23 5.23 0 0 1 6.75 12a5.23 5.23 0 0 1 1.37-3.536ZM10.878 17.13c-.447-.098-.623-.608-.394-1.004l1.733-3.002a.75.75 0 0 1 .65-.375h3.465c.457 0 .81.407.672.842a5.252 5.252 0 0 1-6.126 3.539Z"></path>
<path
fill-rule="evenodd"
d="M21 12.75a.75.75 0 1 0 0-1.5h-.783a8.22 8.22 0 0 0-.237-1.357l.734-.267a.75.75 0 1 0-.513-1.41l-.735.268a8.24 8.24 0 0 0-.689-1.192l.6-.503a.75.75 0 1 0-.964-1.149l-.6.504a8.3 8.3 0 0 0-1.054-.885l.391-.678a.75.75 0 1 0-1.299-.75l-.39.676a8.188 8.188 0 0 0-1.295-.47l.136-.77a.75.75 0 0 0-1.477-.26l-.136.77a8.36 8.36 0 0 0-1.377 0l-.136-.77a.75.75 0 1 0-1.477.26l.136.77c-.448.121-.88.28-1.294.47l-.39-.676a.75.75 0 0 0-1.3.75l.392.678a8.29 8.29 0 0 0-1.054.885l-.6-.504a.75.75 0 1 0-.965 1.149l.6.503a8.243 8.243 0 0 0-.689 1.192L3.8 8.216a.75.75 0 1 0-.513 1.41l.735.267a8.222 8.222 0 0 0-.238 1.356h-.783a.75.75 0 0 0 0 1.5h.783c.042.464.122.917.238 1.356l-.735.268a.75.75 0 0 0 .513 1.41l.735-.268c.197.417.428.816.69 1.191l-.6.504a.75.75 0 0 0 .963 1.15l.601-.505c.326.323.679.62 1.054.885l-.392.68a.75.75 0 0 0 1.3.75l.39-.679c.414.192.847.35 1.294.471l-.136.77a.75.75 0 0 0 1.477.261l.137-.772a8.332 8.332 0 0 0 1.376 0l.136.772a.75.75 0 1 0 1.477-.26l-.136-.771a8.19 8.19 0 0 0 1.294-.47l.391.677a.75.75 0 0 0 1.3-.75l-.393-.679a8.29 8.29 0 0 0 1.054-.885l.601.504a.75.75 0 0 0 .964-1.15l-.6-.503c.261-.375.492-.774.69-1.191l.735.267a.75.75 0 1 0 .512-1.41l-.734-.267c.115-.439.195-.892.237-1.356h.784Zm-2.657-3.06a6.744 6.744 0 0 0-1.19-2.053 6.784 6.784 0 0 0-1.82-1.51A6.705 6.705 0 0 0 12 5.25a6.8 6.8 0 0 0-1.225.11 6.7 6.7 0 0 0-2.15.793 6.784 6.784 0 0 0-2.952 3.489.76.76 0 0 1-.036.098A6.74 6.74 0 0 0 5.251 12a6.74 6.74 0 0 0 3.366 5.842l.009.005a6.704 6.704 0 0 0 2.18.798l.022.003a6.792 6.792 0 0 0 2.368-.004 6.704 6.704 0 0 0 2.205-.811 6.785 6.785 0 0 0 1.762-1.484l.009-.01.009-.01a6.743 6.743 0 0 0 1.18-2.066c.253-.707.39-1.469.39-2.263a6.74 6.74 0 0 0-.408-2.309Z"
clip-rule="evenodd"></path>
</svg>
</span>
Settings
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
<span class="inline-block size-5 w-5 h-5 mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
fill-rule="evenodd"
d="M5.25 9a6.75 6.75 0 0 1 13.5 0v.75c0 2.123.8 4.057 2.118 5.52a.75.75 0 0 1-.297 1.206c-1.544.57-3.16.99-4.831 1.243a3.75 3.75 0 1 1-7.48 0 24.585 24.585 0 0 1-4.831-1.244.75.75 0 0 1-.298-1.205A8.217 8.217 0 0 0 5.25 9.75V9Zm4.502 8.9a2.25 2.25 0 1 0 4.496 0 25.057 25.057 0 0 1-4.496 0Z"
clip-rule="evenodd"></path>
</svg>
</span>
Notifications
</button>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup withIcons>
<Button variant="solid" color="blue" icon="UserIcon">User</Button>
<Button variant="solid" color="blue" icon="CogIcon">Settings</Button>
<Button variant="solid" color="blue" icon="BellIcon">Notifications</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:inline-flex [&>*]:items-center [&>*]:justify-center divide-x divide-gray-300 dark:divide-gray-600 rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
<span class="inline-block size-5 w-5 h-5 mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
fill-rule="evenodd"
d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z"
clip-rule="evenodd"></path>
</svg>
</span>
User
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
<span class="inline-block size-5 w-5 h-5 mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
d="M17.004 10.407c.138.435-.216.842-.672.842h-3.465a.75.75 0 0 1-.65-.375l-1.732-3c-.229-.396-.053-.907.393-1.004a5.252 5.252 0 0 1 6.126 3.537ZM8.12 8.464c.307-.338.838-.235 1.066.16l1.732 3a.75.75 0 0 1 0 .75l-1.732 3c-.229.397-.76.5-1.067.161A5.23 5.23 0 0 1 6.75 12a5.23 5.23 0 0 1 1.37-3.536ZM10.878 17.13c-.447-.098-.623-.608-.394-1.004l1.733-3.002a.75.75 0 0 1 .65-.375h3.465c.457 0 .81.407.672.842a5.252 5.252 0 0 1-6.126 3.539Z"></path>
<path
fill-rule="evenodd"
d="M21 12.75a.75.75 0 1 0 0-1.5h-.783a8.22 8.22 0 0 0-.237-1.357l.734-.267a.75.75 0 1 0-.513-1.41l-.735.268a8.24 8.24 0 0 0-.689-1.192l.6-.503a.75.75 0 1 0-.964-1.149l-.6.504a8.3 8.3 0 0 0-1.054-.885l.391-.678a.75.75 0 1 0-1.299-.75l-.39.676a8.188 8.188 0 0 0-1.295-.47l.136-.77a.75.75 0 0 0-1.477-.26l-.136.77a8.36 8.36 0 0 0-1.377 0l-.136-.77a.75.75 0 1 0-1.477.26l.136.77c-.448.121-.88.28-1.294.47l-.39-.676a.75.75 0 0 0-1.3.75l.392.678a8.29 8.29 0 0 0-1.054.885l-.6-.504a.75.75 0 1 0-.965 1.149l.6.503a8.243 8.243 0 0 0-.689 1.192L3.8 8.216a.75.75 0 1 0-.513 1.41l.735.267a8.222 8.222 0 0 0-.238 1.356h-.783a.75.75 0 0 0 0 1.5h.783c.042.464.122.917.238 1.356l-.735.268a.75.75 0 0 0 .513 1.41l.735-.268c.197.417.428.816.69 1.191l-.6.504a.75.75 0 0 0 .963 1.15l.601-.505c.326.323.679.62 1.054.885l-.392.68a.75.75 0 0 0 1.3.75l.39-.679c.414.192.847.35 1.294.471l-.136.77a.75.75 0 0 0 1.477.261l.137-.772a8.332 8.332 0 0 0 1.376 0l.136.772a.75.75 0 1 0 1.477-.26l-.136-.771a8.19 8.19 0 0 0 1.294-.47l.391.677a.75.75 0 0 0 1.3-.75l-.393-.679a8.29 8.29 0 0 0 1.054-.885l.601.504a.75.75 0 0 0 .964-1.15l-.6-.503c.261-.375.492-.774.69-1.191l.735.267a.75.75 0 1 0 .512-1.41l-.734-.267c.115-.439.195-.892.237-1.356h.784Zm-2.657-3.06a6.744 6.744 0 0 0-1.19-2.053 6.784 6.784 0 0 0-1.82-1.51A6.705 6.705 0 0 0 12 5.25a6.8 6.8 0 0 0-1.225.11 6.7 6.7 0 0 0-2.15.793 6.784 6.784 0 0 0-2.952 3.489.76.76 0 0 1-.036.098A6.74 6.74 0 0 0 5.251 12a6.74 6.74 0 0 0 3.366 5.842l.009.005a6.704 6.704 0 0 0 2.18.798l.022.003a6.792 6.792 0 0 0 2.368-.004 6.704 6.704 0 0 0 2.205-.811 6.785 6.785 0 0 0 1.762-1.484l.009-.01.009-.01a6.743 6.743 0 0 0 1.18-2.066c.253-.707.39-1.469.39-2.263a6.74 6.74 0 0 0-.408-2.309Z"
clip-rule="evenodd"></path>
</svg>
</span>
Settings
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
<span class="inline-block size-5 w-5 h-5 mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
fill-rule="evenodd"
d="M5.25 9a6.75 6.75 0 0 1 13.5 0v.75c0 2.123.8 4.057 2.118 5.52a.75.75 0 0 1-.297 1.206c-1.544.57-3.16.99-4.831 1.243a3.75 3.75 0 1 1-7.48 0 24.585 24.585 0 0 1-4.831-1.244.75.75 0 0 1-.298-1.205A8.217 8.217 0 0 0 5.25 9.75V9Zm4.502 8.9a2.25 2.25 0 1 0 4.496 0 25.057 25.057 0 0 1-4.496 0Z"
clip-rule="evenodd"></path>
</svg>
</span>
Notifications
</button>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup withIcons withDividers={true}>
<Button variant="solid" color="blue" icon="UserIcon">User</Button>
<Button variant="solid" color="blue" icon="CogIcon">Settings</Button>
<Button variant="solid" color="blue" icon="BellIcon">Notifications</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:inline-flex [&>*]:items-center [&>*]:justify-center divide-x divide-gray-300 dark:divide-gray-600 rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-transparent text-blue-500 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-200 hover:bg-blue-200 dark:hover:bg-blue-700 px-3 py-2 text-sm rounded-md">
<span class="inline-block size-5 w-5 h-5 mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
fill-rule="evenodd"
d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z"
clip-rule="evenodd"></path>
</svg>
</span>
User
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-transparent text-blue-500 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-200 hover:bg-blue-200 dark:hover:bg-blue-700 px-3 py-2 text-sm rounded-md">
<span class="inline-block size-5 w-5 h-5 mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
d="M17.004 10.407c.138.435-.216.842-.672.842h-3.465a.75.75 0 0 1-.65-.375l-1.732-3c-.229-.396-.053-.907.393-1.004a5.252 5.252 0 0 1 6.126 3.537ZM8.12 8.464c.307-.338.838-.235 1.066.16l1.732 3a.75.75 0 0 1 0 .75l-1.732 3c-.229.397-.76.5-1.067.161A5.23 5.23 0 0 1 6.75 12a5.23 5.23 0 0 1 1.37-3.536ZM10.878 17.13c-.447-.098-.623-.608-.394-1.004l1.733-3.002a.75.75 0 0 1 .65-.375h3.465c.457 0 .81.407.672.842a5.252 5.252 0 0 1-6.126 3.539Z"></path>
<path
fill-rule="evenodd"
d="M21 12.75a.75.75 0 1 0 0-1.5h-.783a8.22 8.22 0 0 0-.237-1.357l.734-.267a.75.75 0 1 0-.513-1.41l-.735.268a8.24 8.24 0 0 0-.689-1.192l.6-.503a.75.75 0 1 0-.964-1.149l-.6.504a8.3 8.3 0 0 0-1.054-.885l.391-.678a.75.75 0 1 0-1.299-.75l-.39.676a8.188 8.188 0 0 0-1.295-.47l.136-.77a.75.75 0 0 0-1.477-.26l-.136.77a8.36 8.36 0 0 0-1.377 0l-.136-.77a.75.75 0 1 0-1.477.26l.136.77c-.448.121-.88.28-1.294.47l-.39-.676a.75.75 0 0 0-1.3.75l.392.678a8.29 8.29 0 0 0-1.054.885l-.6-.504a.75.75 0 1 0-.965 1.149l.6.503a8.243 8.243 0 0 0-.689 1.192L3.8 8.216a.75.75 0 1 0-.513 1.41l.735.267a8.222 8.222 0 0 0-.238 1.356h-.783a.75.75 0 0 0 0 1.5h.783c.042.464.122.917.238 1.356l-.735.268a.75.75 0 0 0 .513 1.41l.735-.268c.197.417.428.816.69 1.191l-.6.504a.75.75 0 0 0 .963 1.15l.601-.505c.326.323.679.62 1.054.885l-.392.68a.75.75 0 0 0 1.3.75l.39-.679c.414.192.847.35 1.294.471l-.136.77a.75.75 0 0 0 1.477.261l.137-.772a8.332 8.332 0 0 0 1.376 0l.136.772a.75.75 0 1 0 1.477-.26l-.136-.771a8.19 8.19 0 0 0 1.294-.47l.391.677a.75.75 0 0 0 1.3-.75l-.393-.679a8.29 8.29 0 0 0 1.054-.885l.601.504a.75.75 0 0 0 .964-1.15l-.6-.503c.261-.375.492-.774.69-1.191l.735.267a.75.75 0 1 0 .512-1.41l-.734-.267c.115-.439.195-.892.237-1.356h.784Zm-2.657-3.06a6.744 6.744 0 0 0-1.19-2.053 6.784 6.784 0 0 0-1.82-1.51A6.705 6.705 0 0 0 12 5.25a6.8 6.8 0 0 0-1.225.11 6.7 6.7 0 0 0-2.15.793 6.784 6.784 0 0 0-2.952 3.489.76.76 0 0 1-.036.098A6.74 6.74 0 0 0 5.251 12a6.74 6.74 0 0 0 3.366 5.842l.009.005a6.704 6.704 0 0 0 2.18.798l.022.003a6.792 6.792 0 0 0 2.368-.004 6.704 6.704 0 0 0 2.205-.811 6.785 6.785 0 0 0 1.762-1.484l.009-.01.009-.01a6.743 6.743 0 0 0 1.18-2.066c.253-.707.39-1.469.39-2.263a6.74 6.74 0 0 0-.408-2.309Z"
clip-rule="evenodd"></path>
</svg>
</span>
Settings
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-transparent text-blue-500 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-200 hover:bg-blue-200 dark:hover:bg-blue-700 px-3 py-2 text-sm rounded-md">
<span class="inline-block size-5 w-5 h-5 mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
fill-rule="evenodd"
d="M5.25 9a6.75 6.75 0 0 1 13.5 0v.75c0 2.123.8 4.057 2.118 5.52a.75.75 0 0 1-.297 1.206c-1.544.57-3.16.99-4.831 1.243a3.75 3.75 0 1 1-7.48 0 24.585 24.585 0 0 1-4.831-1.244.75.75 0 0 1-.298-1.205A8.217 8.217 0 0 0 5.25 9.75V9Zm4.502 8.9a2.25 2.25 0 1 0 4.496 0 25.057 25.057 0 0 1-4.496 0Z"
clip-rule="evenodd"></path>
</svg>
</span>
Notifications
</button>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup withIcons withDividers={true}>
<Button variant="ghost" color="blue" icon="UserIcon">User</Button>
<Button variant="ghost" color="blue" icon="CogIcon">Settings</Button>
<Button variant="ghost" color="blue" icon="BellIcon">Notifications</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
<div
class="inline-flex overflow-hidden flex-col [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:inline-flex [&>*]:items-center [&>*]:justify-center rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-t-md [&>*:last-child]:rounded-b-md [&>*:not(:first-child)]:-mt-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
<span class="inline-block size-5 w-5 h-5 mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
fill-rule="evenodd"
d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z"
clip-rule="evenodd"></path>
</svg>
</span>
User
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
<span class="inline-block size-5 w-5 h-5 mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
d="M17.004 10.407c.138.435-.216.842-.672.842h-3.465a.75.75 0 0 1-.65-.375l-1.732-3c-.229-.396-.053-.907.393-1.004a5.252 5.252 0 0 1 6.126 3.537ZM8.12 8.464c.307-.338.838-.235 1.066.16l1.732 3a.75.75 0 0 1 0 .75l-1.732 3c-.229.397-.76.5-1.067.161A5.23 5.23 0 0 1 6.75 12a5.23 5.23 0 0 1 1.37-3.536ZM10.878 17.13c-.447-.098-.623-.608-.394-1.004l1.733-3.002a.75.75 0 0 1 .65-.375h3.465c.457 0 .81.407.672.842a5.252 5.252 0 0 1-6.126 3.539Z"></path>
<path
fill-rule="evenodd"
d="M21 12.75a.75.75 0 1 0 0-1.5h-.783a8.22 8.22 0 0 0-.237-1.357l.734-.267a.75.75 0 1 0-.513-1.41l-.735.268a8.24 8.24 0 0 0-.689-1.192l.6-.503a.75.75 0 1 0-.964-1.149l-.6.504a8.3 8.3 0 0 0-1.054-.885l.391-.678a.75.75 0 1 0-1.299-.75l-.39.676a8.188 8.188 0 0 0-1.295-.47l.136-.77a.75.75 0 0 0-1.477-.26l-.136.77a8.36 8.36 0 0 0-1.377 0l-.136-.77a.75.75 0 1 0-1.477.26l.136.77c-.448.121-.88.28-1.294.47l-.39-.676a.75.75 0 0 0-1.3.75l.392.678a8.29 8.29 0 0 0-1.054.885l-.6-.504a.75.75 0 1 0-.965 1.149l.6.503a8.243 8.243 0 0 0-.689 1.192L3.8 8.216a.75.75 0 1 0-.513 1.41l.735.267a8.222 8.222 0 0 0-.238 1.356h-.783a.75.75 0 0 0 0 1.5h.783c.042.464.122.917.238 1.356l-.735.268a.75.75 0 0 0 .513 1.41l.735-.268c.197.417.428.816.69 1.191l-.6.504a.75.75 0 0 0 .963 1.15l.601-.505c.326.323.679.62 1.054.885l-.392.68a.75.75 0 0 0 1.3.75l.39-.679c.414.192.847.35 1.294.471l-.136.77a.75.75 0 0 0 1.477.261l.137-.772a8.332 8.332 0 0 0 1.376 0l.136.772a.75.75 0 1 0 1.477-.26l-.136-.771a8.19 8.19 0 0 0 1.294-.47l.391.677a.75.75 0 0 0 1.3-.75l-.393-.679a8.29 8.29 0 0 0 1.054-.885l.601.504a.75.75 0 0 0 .964-1.15l-.6-.503c.261-.375.492-.774.69-1.191l.735.267a.75.75 0 1 0 .512-1.41l-.734-.267c.115-.439.195-.892.237-1.356h.784Zm-2.657-3.06a6.744 6.744 0 0 0-1.19-2.053 6.784 6.784 0 0 0-1.82-1.51A6.705 6.705 0 0 0 12 5.25a6.8 6.8 0 0 0-1.225.11 6.7 6.7 0 0 0-2.15.793 6.784 6.784 0 0 0-2.952 3.489.76.76 0 0 1-.036.098A6.74 6.74 0 0 0 5.251 12a6.74 6.74 0 0 0 3.366 5.842l.009.005a6.704 6.704 0 0 0 2.18.798l.022.003a6.792 6.792 0 0 0 2.368-.004 6.704 6.704 0 0 0 2.205-.811 6.785 6.785 0 0 0 1.762-1.484l.009-.01.009-.01a6.743 6.743 0 0 0 1.18-2.066c.253-.707.39-1.469.39-2.263a6.74 6.74 0 0 0-.408-2.309Z"
clip-rule="evenodd"></path>
</svg>
</span>
Settings
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
<span class="inline-block size-5 w-5 h-5 mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
fill-rule="evenodd"
d="M5.25 9a6.75 6.75 0 0 1 13.5 0v.75c0 2.123.8 4.057 2.118 5.52a.75.75 0 0 1-.297 1.206c-1.544.57-3.16.99-4.831 1.243a3.75 3.75 0 1 1-7.48 0 24.585 24.585 0 0 1-4.831-1.244.75.75 0 0 1-.298-1.205A8.217 8.217 0 0 0 5.25 9.75V9Zm4.502 8.9a2.25 2.25 0 1 0 4.496 0 25.057 25.057 0 0 1-4.496 0Z"
clip-rule="evenodd"></path>
</svg>
</span>
Notifications
</button>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup orientation="vertical" withIcons>
<Button variant="solid" color="blue" icon="UserIcon">User</Button>
<Button variant="solid" color="blue" icon="CogIcon">Settings</Button>
<Button variant="solid" color="blue" icon="BellIcon">Notifications</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Solid
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-transparent text-blue-500 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-200 border-blue-500 dark:border-blue-400 hover:border-blue-700 dark:hover:border-blue-200 border px-3 py-2 text-sm rounded-md">
Outline
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-transparent text-blue-500 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-200 hover:bg-blue-200 dark:hover:bg-blue-700 px-3 py-2 text-sm rounded-md">
Ghost
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-100 dark:bg-blue-800 hover:bg-blue-200 dark:hover:bg-blue-700 text-blue-700 dark:text-blue-100 border-blue-200 dark:border-blue-600 px-3 py-2 text-sm rounded-md">
Soft
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 text-blue-700 dark:text-blue-100 hover:opacity-70 px-3 py-2 text-sm rounded-md">
Text
</button>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup>
<Button variant="solid" color="blue">Solid</Button>
<Button variant="outline" color="blue">Outline</Button>
<Button variant="ghost" color="blue">Ghost</Button>
<Button variant="soft" color="blue">Soft</Button>
<Button variant="text" color="blue">Text</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
Button Group with and without Border
Comparison of ButtonGroup with showBorder prop set to true and false.
<div class="space-y-4">
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
With Border
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
With Border
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
With Border
</button>
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
No Border
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
No Border
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
No Border
</button>
</div>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup showBorder={true}>
<Button variant="solid" color="blue">With Border</Button>
<Button variant="solid" color="blue">With Border</Button>
<Button variant="solid" color="blue">With Border</Button>
</ButtonGroup>
<ButtonGroup showBorder={false}>
<Button variant="solid" color="blue">No Border</Button>
<Button variant="solid" color="blue">No Border</Button>
<Button variant="solid" color="blue">No Border</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px shadow-lg rounded-full"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-purple-500 hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-500 text-white dark:text-white hover:text-purple-100 dark:hover:text-purple-200 px-3 py-2 text-sm rounded-md">
Custom
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-purple-500 hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-500 text-white dark:text-white hover:text-purple-100 dark:hover:text-purple-200 px-3 py-2 text-sm rounded-md">
Styled
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-purple-500 hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-500 text-white dark:text-white hover:text-purple-100 dark:hover:text-purple-200 px-3 py-2 text-sm rounded-md">
Group
</button>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup class="shadow-lg rounded-full">
<Button variant="solid" color="purple">Custom</Button>
<Button variant="solid" color="purple">Styled</Button>
<Button variant="solid" color="purple">Group</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
<div
class="inline-flex overflow-hidden flex-col [&>*]:px-4 [&>*]:py-2 [&>*]:text-base [&>*]:inline-flex [&>*]:items-center [&>*]:justify-center divide-y divide-gray-300 dark:divide-gray-600 rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-t-md [&>*:last-child]:rounded-b-md [&>*:not(:first-child)]:-mt-px max-w-xs mx-auto shadow-xl"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-indigo-500 hover:bg-indigo-600 dark:bg-indigo-600 dark:hover:bg-indigo-500 text-white dark:text-white hover:text-indigo-100 dark:hover:text-indigo-200 px-3 py-2 text-sm rounded-md">
<span class="inline-block size-5 w-5 h-5 mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
fill-rule="evenodd"
d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z"
clip-rule="evenodd"></path>
</svg>
</span>
Profile
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-indigo-500 hover:bg-indigo-600 dark:bg-indigo-600 dark:hover:bg-indigo-500 text-white dark:text-white hover:text-indigo-100 dark:hover:text-indigo-200 px-3 py-2 text-sm rounded-md">
<span class="inline-block size-5 w-5 h-5 mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
d="M17.004 10.407c.138.435-.216.842-.672.842h-3.465a.75.75 0 0 1-.65-.375l-1.732-3c-.229-.396-.053-.907.393-1.004a5.252 5.252 0 0 1 6.126 3.537ZM8.12 8.464c.307-.338.838-.235 1.066.16l1.732 3a.75.75 0 0 1 0 .75l-1.732 3c-.229.397-.76.5-1.067.161A5.23 5.23 0 0 1 6.75 12a5.23 5.23 0 0 1 1.37-3.536ZM10.878 17.13c-.447-.098-.623-.608-.394-1.004l1.733-3.002a.75.75 0 0 1 .65-.375h3.465c.457 0 .81.407.672.842a5.252 5.252 0 0 1-6.126 3.539Z"></path>
<path
fill-rule="evenodd"
d="M21 12.75a.75.75 0 1 0 0-1.5h-.783a8.22 8.22 0 0 0-.237-1.357l.734-.267a.75.75 0 1 0-.513-1.41l-.735.268a8.24 8.24 0 0 0-.689-1.192l.6-.503a.75.75 0 1 0-.964-1.149l-.6.504a8.3 8.3 0 0 0-1.054-.885l.391-.678a.75.75 0 1 0-1.299-.75l-.39.676a8.188 8.188 0 0 0-1.295-.47l.136-.77a.75.75 0 0 0-1.477-.26l-.136.77a8.36 8.36 0 0 0-1.377 0l-.136-.77a.75.75 0 1 0-1.477.26l.136.77c-.448.121-.88.28-1.294.47l-.39-.676a.75.75 0 0 0-1.3.75l.392.678a8.29 8.29 0 0 0-1.054.885l-.6-.504a.75.75 0 1 0-.965 1.149l.6.503a8.243 8.243 0 0 0-.689 1.192L3.8 8.216a.75.75 0 1 0-.513 1.41l.735.267a8.222 8.222 0 0 0-.238 1.356h-.783a.75.75 0 0 0 0 1.5h.783c.042.464.122.917.238 1.356l-.735.268a.75.75 0 0 0 .513 1.41l.735-.268c.197.417.428.816.69 1.191l-.6.504a.75.75 0 0 0 .963 1.15l.601-.505c.326.323.679.62 1.054.885l-.392.68a.75.75 0 0 0 1.3.75l.39-.679c.414.192.847.35 1.294.471l-.136.77a.75.75 0 0 0 1.477.261l.137-.772a8.332 8.332 0 0 0 1.376 0l.136.772a.75.75 0 1 0 1.477-.26l-.136-.771a8.19 8.19 0 0 0 1.294-.47l.391.677a.75.75 0 0 0 1.3-.75l-.393-.679a8.29 8.29 0 0 0 1.054-.885l.601.504a.75.75 0 0 0 .964-1.15l-.6-.503c.261-.375.492-.774.69-1.191l.735.267a.75.75 0 1 0 .512-1.41l-.734-.267c.115-.439.195-.892.237-1.356h.784Zm-2.657-3.06a6.744 6.744 0 0 0-1.19-2.053 6.784 6.784 0 0 0-1.82-1.51A6.705 6.705 0 0 0 12 5.25a6.8 6.8 0 0 0-1.225.11 6.7 6.7 0 0 0-2.15.793 6.784 6.784 0 0 0-2.952 3.489.76.76 0 0 1-.036.098A6.74 6.74 0 0 0 5.251 12a6.74 6.74 0 0 0 3.366 5.842l.009.005a6.704 6.704 0 0 0 2.18.798l.022.003a6.792 6.792 0 0 0 2.368-.004 6.704 6.704 0 0 0 2.205-.811 6.785 6.785 0 0 0 1.762-1.484l.009-.01.009-.01a6.743 6.743 0 0 0 1.18-2.066c.253-.707.39-1.469.39-2.263a6.74 6.74 0 0 0-.408-2.309Z"
clip-rule="evenodd"></path>
</svg>
</span>
Settings
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-indigo-500 hover:bg-indigo-600 dark:bg-indigo-600 dark:hover:bg-indigo-500 text-white dark:text-white hover:text-indigo-100 dark:hover:text-indigo-200 px-3 py-2 text-sm rounded-md">
<span class="inline-block size-5 w-5 h-5 mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
fill-rule="evenodd"
d="M5.25 9a6.75 6.75 0 0 1 13.5 0v.75c0 2.123.8 4.057 2.118 5.52a.75.75 0 0 1-.297 1.206c-1.544.57-3.16.99-4.831 1.243a3.75 3.75 0 1 1-7.48 0 24.585 24.585 0 0 1-4.831-1.244.75.75 0 0 1-.298-1.205A8.217 8.217 0 0 0 5.25 9.75V9Zm4.502 8.9a2.25 2.25 0 1 0 4.496 0 25.057 25.057 0 0 1-4.496 0Z"
clip-rule="evenodd"></path>
</svg>
</span>
Notifications
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-indigo-500 hover:bg-indigo-600 dark:bg-indigo-600 dark:hover:bg-indigo-500 text-white dark:text-white hover:text-indigo-100 dark:hover:text-indigo-200 px-3 py-2 text-sm rounded-md">
<span class="inline-block size-5 w-5 h-5 mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
fill-rule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm11.378-3.917c-.89-.777-2.366-.777-3.255 0a.75.75 0 0 1-.988-1.129c1.454-1.272 3.776-1.272 5.23 0 1.513 1.324 1.513 3.518 0 4.842a3.75 3.75 0 0 1-.837.552c-.676.328-1.028.774-1.028 1.152v.75a.75.75 0 0 1-1.5 0v-.75c0-1.279 1.06-2.107 1.875-2.502.182-.088.351-.199.503-.331.83-.727.83-1.857 0-2.584ZM12 18a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"
clip-rule="evenodd"></path>
</svg>
<span class="sr-only">Unknown icon</span>
</span>
Logout
</button>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup
orientation="vertical"
size="lg"
withIcons
withDividers={true}
showBorder={true}
class="max-w-xs mx-auto shadow-xl"
>
<Button variant="solid" color="indigo" icon="UserIcon">Profile</Button>
<Button variant="solid" color="indigo" icon="CogIcon">Settings</Button>
<Button variant="solid" color="indigo" icon="BellIcon">Notifications</Button>
<Button variant="solid" color="indigo" icon="LogoutIcon">Logout</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
<div class="space-y-4 grid grid-cols-1 w-fit">
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Default
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Shape
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Buttons
</button>
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full [&>*]:justify-center"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-indigo-500 hover:bg-indigo-600 dark:bg-indigo-600 dark:hover:bg-indigo-500 text-white dark:text-white hover:text-indigo-100 dark:hover:text-indigo-200 px-3 py-2 text-sm rounded-md">
Pill
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-indigo-500 hover:bg-indigo-600 dark:bg-indigo-600 dark:hover:bg-indigo-500 text-white dark:text-white hover:text-indigo-100 dark:hover:text-indigo-200 px-3 py-2 text-sm rounded-md">
Shape
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-indigo-500 hover:bg-indigo-600 dark:bg-indigo-600 dark:hover:bg-indigo-500 text-white dark:text-white hover:text-indigo-100 dark:hover:text-indigo-200 px-3 py-2 text-sm rounded-md">
Buttons
</button>
</div>
<div
class="inline-flex overflow-hidden flex-row [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm rounded-none [&>*]:rounded-none [&>*]:justify-center"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-purple-500 hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-500 text-white dark:text-white hover:text-purple-100 dark:hover:text-purple-200 px-3 py-2 text-sm rounded-md">
Square
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-purple-500 hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-500 text-white dark:text-white hover:text-purple-100 dark:hover:text-purple-200 px-3 py-2 text-sm rounded-md">
Shape
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-purple-500 hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-500 text-white dark:text-white hover:text-purple-100 dark:hover:text-purple-200 px-3 py-2 text-sm rounded-md">
Buttons
</button>
</div>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup shape="default">
<Button variant="solid" color="blue">Default</Button>
<Button variant="solid" color="blue">Shape</Button>
<Button variant="solid" color="blue">Buttons</Button>
</ButtonGroup>
<ButtonGroup shape="pill">
<Button variant="solid" color="indigo">Pill</Button>
<Button variant="solid" color="indigo">Shape</Button>
<Button variant="solid" color="indigo">Buttons</Button>
</ButtonGroup>
<ButtonGroup shape="square">
<Button variant="solid" color="purple">Square</Button>
<Button variant="solid" color="purple">Shape</Button>
<Button variant="solid" color="purple">Buttons</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
Button Group Alignments
Button groups with different content alignments: left, center, and right.
<div class="grid grid-cols-3 w-fit gap-8">
<div
class="inline-flex overflow-hidden flex-col [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-start rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-t-md [&>*:last-child]:rounded-b-md [&>*:not(:first-child)]:-mt-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Left
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Aligned
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 text-white dark:text-white hover:text-blue-100 dark:hover:text-blue-200 px-3 py-2 text-sm rounded-md">
Buttons
</button>
</div>
<div
class="inline-flex overflow-hidden flex-col [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-center rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-t-md [&>*:last-child]:rounded-b-md [&>*:not(:first-child)]:-mt-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-purple-500 hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-500 text-white dark:text-white hover:text-purple-100 dark:hover:text-purple-200 px-3 py-2 text-sm rounded-md">
Center
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-purple-500 hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-500 text-white dark:text-white hover:text-purple-100 dark:hover:text-purple-200 px-3 py-2 text-sm rounded-md">
Aligned
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-purple-500 hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-500 text-white dark:text-white hover:text-purple-100 dark:hover:text-purple-200 px-3 py-2 text-sm rounded-md">
Buttons
</button>
</div>
<div
class="inline-flex overflow-hidden flex-col [&>*]:px-3 [&>*]:py-2 [&>*]:text-sm [&>*]:justify-end rounded-md [&>*]:rounded-none [&>*:first-child]:rounded-t-md [&>*:last-child]:rounded-b-md [&>*:not(:first-child)]:-mt-px"
role="group">
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-indigo-500 hover:bg-indigo-600 dark:bg-indigo-600 dark:hover:bg-indigo-500 text-white dark:text-white hover:text-indigo-100 dark:hover:text-indigo-200 px-3 py-2 text-sm rounded-md">
Right
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-indigo-500 hover:bg-indigo-600 dark:bg-indigo-600 dark:hover:bg-indigo-500 text-white dark:text-white hover:text-indigo-100 dark:hover:text-indigo-200 px-3 py-2 text-sm rounded-md">
Aligned
</button>
<button
class="inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-indigo-500 hover:bg-indigo-600 dark:bg-indigo-600 dark:hover:bg-indigo-500 text-white dark:text-white hover:text-indigo-100 dark:hover:text-indigo-200 px-3 py-2 text-sm rounded-md">
Buttons
</button>
</div>
</div>
---
import { ButtonGroup } from '@/components/ui/buttongroup';
import { Button } from '@/components/ui/button';
---
<ButtonGroup alignment="left" orientation="vertical">
<Button variant="solid" color="blue">Left</Button>
<Button variant="solid" color="blue">Aligned</Button>
<Button variant="solid" color="blue">Buttons</Button>
</ButtonGroup>
<ButtonGroup alignment="center" orientation="vertical">
<Button variant="solid" color="purple">Center</Button>
<Button variant="solid" color="purple">Aligned</Button>
<Button variant="solid" color="purple">Buttons</Button>
</ButtonGroup>
<ButtonGroup alignment="right" orientation="vertical">
<Button variant="solid" color="indigo">Right</Button>
<Button variant="solid" color="indigo">Aligned</Button>
<Button variant="solid" color="indigo">Buttons</Button>
</ButtonGroup> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import { twMerge } from "tailwind-merge";
// Types and Interfaces
type ButtonGroupVariants = VariantProps<typeof buttonGroupStyles>;
interface Props extends HTMLAttributes<"div">, ButtonGroupVariants {
orientation?: "horizontal" | "vertical";
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
items?: 2 | 3 | 4 | 5 | 6;
withIcons?: boolean;
withDividers?: boolean;
shape?: "default" | "pill" | "square";
alignment?: "left" | "center" | "right";
class?: string;
}
// Styles
const buttonGroupStyles = tv({
base: "inline-flex overflow-hidden",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xs: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-xs",
sm: "[&>*]:px-2 [&>*]:py-1 [&>*]:text-sm",
md: "[&>*]:px-3 [&>*]:py-2 [&>*]:text-sm",
lg: "[&>*]:px-4 [&>*]:py-2 [&>*]:text-base",
xl: "[&>*]:px-5 [&>*]:py-3 [&>*]:text-lg",
"2xl": "[&>*]:px-6 [&>*]:py-4 [&>*]:text-xl",
},
items: {
2: "",
3: "",
4: "",
5: "",
6: "",
},
withIcons: {
true: "[&>*]:inline-flex [&>*]:items-center [&>*]:justify-center",
false: "",
},
withDividers: {
true: "",
false: "",
},
shape: {
default: "",
pill: "rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-l-full [&>*:last-child]:rounded-r-full",
square: "rounded-none [&>*]:rounded-none",
},
alignment: {
left: "[&>*]:justify-start",
center: "[&>*]:justify-center",
right: "[&>*]:justify-end",
},
},
compoundVariants: [
{
orientation: "horizontal",
withDividers: true,
class: "divide-x divide-gray-300 dark:divide-gray-600",
},
{
orientation: "vertical",
withDividers: true,
class: "divide-y divide-gray-300 dark:divide-gray-600",
},
{
orientation: "horizontal",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-l-md
[&>*:last-child]:rounded-r-md
[&>*:not(:first-child)]:-ml-px
`,
},
{
orientation: "vertical",
shape: "default",
class: `
rounded-md
[&>*]:rounded-none
[&>*:first-child]:rounded-t-md
[&>*:last-child]:rounded-b-md
[&>*:not(:first-child)]:-mt-px
`,
},
{
orientation: "vertical",
shape: "pill",
class:
"rounded-full [&>*]:rounded-none [&>*:first-child]:rounded-t-full [&>*:last-child]:rounded-b-full",
},
],
defaultVariants: {
orientation: "horizontal",
size: "md",
items: 3,
withIcons: false,
withDividers: false,
shape: "default",
alignment: "center",
},
});
// Component Logic
const {
orientation = "horizontal",
size = "md",
items = 3,
withIcons = false,
withDividers = false,
shape = "default",
alignment = "center",
class: className = "",
} = Astro.props as Props;
const containerClasses = twMerge(
buttonGroupStyles({
orientation,
size,
items,
withIcons,
withDividers,
shape,
alignment,
}),
className,
);
// PropTypes for documentation
export const propTypes = {
orientation: {
type: ["horizontal", "vertical"],
description: "Orientation of the button group",
default: "horizontal",
},
size: {
type: ["xs", "sm", "md", "lg", "xl", "2xl"],
description: "Size of the buttons in the group",
default: "md",
},
items: {
type: [2, 3, 4, 5, 6],
description: "Number of items in the button group",
default: 3,
},
withIcons: {
type: "boolean",
description: "Whether the buttons contain icons",
default: false,
},
withDividers: {
type: "boolean",
description: "Whether to include dividers between buttons",
default: false,
},
shape: {
type: ["default", "pill", "square"],
description: "Shape of the button group",
default: "default",
},
alignment: {
type: ["left", "center", "right"],
description: "Alignment of content within the buttons",
default: "center",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the button group",
},
};
---
<div class={containerClasses} role="group">
<slot />
</div>
Component Properties
| Property | Type | Default | Description |
|---|---|---|---|
| orientation | horizontal | vertical | "horizontal" | Orientation of the button group |
| size | xs | sm | md | lg | xl | 2xl | "md" | Size of the buttons in the group |
| items | 2 | 3 | 4 | 5 | 6 | 3 | Number of items in the button group |
| withIcons | boolean | false | Whether the buttons contain icons |
| withDividers | boolean | false | Whether to include dividers between buttons |
| shape | default | pill | square | "default" | Shape of the button group |
| alignment | left | center | right | "center" | Alignment of content within the buttons |
| class | string | - | Additional CSS classes to apply to the button group |