Badges
Compact label components for displaying short pieces of information. Supports various styles, colors, and customization options. previews<span
class="inline-flex items-center font-medium bg-blue-500 dark:bg-blue-600 text-white dark:text-white border-blue-600 dark:border-blue-500 rounded px-2 py-1 text-sm">
<p>Default</p>
</span>
---
import { Badge } from '@/components/ui/badge';
---
<Badge>
Default
</Badge> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import {
getOutlinedClasses,
type ColorName,
getDefaultClasses,
getSoftClasses,
colorPalette,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
// PropTypes for documentation
export const propTypes = {
content: {
type: "string | JSX.Element",
description: "The content of the badge",
},
icon: {
type: "string | { name: string; class?: string }",
description: "The icon to display in the badge",
},
iconPosition: {
type: ["left", "right"],
description: "The position of the icon",
default: "left",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the badge",
},
removable: {
type: "boolean",
description: "Whether the badge is removable",
default: false,
},
variant: {
type: ["solid", "soft", "outline"],
description: "The variant of the badge",
default: "solid",
},
color: {
type: Object.keys(colorPalette),
description: "The color scheme for the badge",
default: "blue",
},
shape: {
type: ["rounded", "square", "pill"],
description: "The shape of the badge",
default: "rounded",
},
size: {
type: ["sm", "md", "lg"],
description: "The size of the badge",
default: "md",
},
maxWidth: {
type: "string",
description: "The maximum width of the badge",
},
animated: {
type: "boolean",
description: "Whether the badge should have animations",
default: false,
},
};
// Types and Interfaces
type BadgeVariants = VariantProps<typeof badgeStyles>;
interface Props extends HTMLAttributes<"span">, BadgeVariants {
content?: string | JSX.Element;
icon?: string | { name: string; class?: string };
iconPosition?: "left" | "right";
class?: string;
removable?: boolean;
variant?: "solid" | "soft" | "outline";
color?: ColorName;
shape?: "rounded" | "square" | "pill";
size?: "sm" | "md" | "lg";
maxWidth?: string;
animated?: boolean;
}
// Component Logic
const {
content,
icon,
iconPosition = "left",
variant = "solid",
color = "blue",
removable = false,
shape = "rounded",
class: className = "",
size = "md",
maxWidth,
animated = false,
} = Astro.props as Props;
// Styles
const badgeStyles = tv({
base: "inline-flex items-center font-medium",
variants: {
variant: {
solid: getDefaultClasses(color, { includeHover: false }),
soft: getSoftClasses(color, { includeHover: false }),
outline: `${getOutlinedClasses(color, { includeHover: false })} border`,
},
shape: {
rounded: "rounded",
square: "",
pill: "rounded-full",
},
size: {
sm: "px-2 py-1 text-xs",
md: "px-2 py-1 text-sm",
lg: "px-3 py-1 text-base",
},
animated: {
true: "transition-all duration-300 ease-in-out hover:scale-105",
false: "",
},
},
defaultVariants: {
variant: "solid",
shape: "rounded",
size: "md",
animated: false,
},
});
const finalIcon = typeof icon === "string" ? { name: icon, class: "" } : icon;
---
<span
class={twMerge(
badgeStyles({ variant, shape, size, animated }),
maxWidth && `max-w-[${maxWidth}] truncate`,
className,
)}
>
{
finalIcon && iconPosition === "left" && (
<Icon
name={finalIcon.name}
class={twMerge(
`mr-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{content && <Fragment set:html={content} />}
<slot />
{
finalIcon && iconPosition === "right" && (
<Icon
name={finalIcon.name}
class={twMerge(
`ml-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{
removable && (
<button
type="button"
class={twMerge(
"ml-1 -mr-1 hover:bg-opacity-10 hover:bg-black focus:outline-none focus:ring-2 focus:ring-offset-2",
`focus:ring-${color}-500 rounded-${shape === "pill" ? "full" : "sm"}`,
)}
aria-label="Remove"
>
<Icon
name="XMarkIcon"
class={twMerge(
size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4",
)}
aria-hidden="true"
/>
</button>
)
}
</span>
<span
class="inline-flex items-center font-medium bg-blue-500 dark:bg-blue-600 text-white dark:text-white border-blue-600 dark:border-blue-500 rounded px-2 py-1 text-sm">
Solid
</span>
<span
class="inline-flex items-center font-medium bg-blue-100 dark:bg-blue-800 text-blue-700 dark:text-blue-100 border-blue-200 dark:border-blue-600 rounded px-2 py-1 text-sm ml-2">
Soft
</span>
<span
class="inline-flex items-center font-medium bg-transparent text-blue-500 dark:text-blue-400 border-blue-500 dark:border-blue-400 border rounded px-2 py-1 text-sm ml-2">
Outline
</span>
---
import { Badge } from '@/components/ui/badge';
---
<Badge variant="solid">Solid</Badge>
<Badge variant="soft">Soft</Badge>
<Badge variant="outline">Outline</Badge> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import {
getOutlinedClasses,
type ColorName,
getDefaultClasses,
getSoftClasses,
colorPalette,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
// PropTypes for documentation
export const propTypes = {
content: {
type: "string | JSX.Element",
description: "The content of the badge",
},
icon: {
type: "string | { name: string; class?: string }",
description: "The icon to display in the badge",
},
iconPosition: {
type: ["left", "right"],
description: "The position of the icon",
default: "left",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the badge",
},
removable: {
type: "boolean",
description: "Whether the badge is removable",
default: false,
},
variant: {
type: ["solid", "soft", "outline"],
description: "The variant of the badge",
default: "solid",
},
color: {
type: Object.keys(colorPalette),
description: "The color scheme for the badge",
default: "blue",
},
shape: {
type: ["rounded", "square", "pill"],
description: "The shape of the badge",
default: "rounded",
},
size: {
type: ["sm", "md", "lg"],
description: "The size of the badge",
default: "md",
},
maxWidth: {
type: "string",
description: "The maximum width of the badge",
},
animated: {
type: "boolean",
description: "Whether the badge should have animations",
default: false,
},
};
// Types and Interfaces
type BadgeVariants = VariantProps<typeof badgeStyles>;
interface Props extends HTMLAttributes<"span">, BadgeVariants {
content?: string | JSX.Element;
icon?: string | { name: string; class?: string };
iconPosition?: "left" | "right";
class?: string;
removable?: boolean;
variant?: "solid" | "soft" | "outline";
color?: ColorName;
shape?: "rounded" | "square" | "pill";
size?: "sm" | "md" | "lg";
maxWidth?: string;
animated?: boolean;
}
// Component Logic
const {
content,
icon,
iconPosition = "left",
variant = "solid",
color = "blue",
removable = false,
shape = "rounded",
class: className = "",
size = "md",
maxWidth,
animated = false,
} = Astro.props as Props;
// Styles
const badgeStyles = tv({
base: "inline-flex items-center font-medium",
variants: {
variant: {
solid: getDefaultClasses(color, { includeHover: false }),
soft: getSoftClasses(color, { includeHover: false }),
outline: `${getOutlinedClasses(color, { includeHover: false })} border`,
},
shape: {
rounded: "rounded",
square: "",
pill: "rounded-full",
},
size: {
sm: "px-2 py-1 text-xs",
md: "px-2 py-1 text-sm",
lg: "px-3 py-1 text-base",
},
animated: {
true: "transition-all duration-300 ease-in-out hover:scale-105",
false: "",
},
},
defaultVariants: {
variant: "solid",
shape: "rounded",
size: "md",
animated: false,
},
});
const finalIcon = typeof icon === "string" ? { name: icon, class: "" } : icon;
---
<span
class={twMerge(
badgeStyles({ variant, shape, size, animated }),
maxWidth && `max-w-[${maxWidth}] truncate`,
className,
)}
>
{
finalIcon && iconPosition === "left" && (
<Icon
name={finalIcon.name}
class={twMerge(
`mr-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{content && <Fragment set:html={content} />}
<slot />
{
finalIcon && iconPosition === "right" && (
<Icon
name={finalIcon.name}
class={twMerge(
`ml-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{
removable && (
<button
type="button"
class={twMerge(
"ml-1 -mr-1 hover:bg-opacity-10 hover:bg-black focus:outline-none focus:ring-2 focus:ring-offset-2",
`focus:ring-${color}-500 rounded-${shape === "pill" ? "full" : "sm"}`,
)}
aria-label="Remove"
>
<Icon
name="XMarkIcon"
class={twMerge(
size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4",
)}
aria-hidden="true"
/>
</button>
)
}
</span>
<span
class="inline-flex items-center font-medium bg-blue-500 dark:bg-blue-600 text-white dark:text-white border-blue-600 dark:border-blue-500 rounded px-2 py-1 text-sm">
Rounded
</span>
<span
class="inline-flex items-center font-medium bg-blue-500 dark:bg-blue-600 text-white dark:text-white border-blue-600 dark:border-blue-500 px-2 py-1 text-sm ml-2">
Square
</span>
<span
class="inline-flex items-center font-medium bg-blue-500 dark:bg-blue-600 text-white dark:text-white border-blue-600 dark:border-blue-500 rounded-full px-2 py-1 text-sm ml-2">
Pill
</span>
---
import { Badge } from '@/components/ui/badge';
---
<Badge shape="rounded">Rounded</Badge>
<Badge shape="square">Square</Badge>
<Badge shape="pill">Pill</Badge> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import {
getOutlinedClasses,
type ColorName,
getDefaultClasses,
getSoftClasses,
colorPalette,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
// PropTypes for documentation
export const propTypes = {
content: {
type: "string | JSX.Element",
description: "The content of the badge",
},
icon: {
type: "string | { name: string; class?: string }",
description: "The icon to display in the badge",
},
iconPosition: {
type: ["left", "right"],
description: "The position of the icon",
default: "left",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the badge",
},
removable: {
type: "boolean",
description: "Whether the badge is removable",
default: false,
},
variant: {
type: ["solid", "soft", "outline"],
description: "The variant of the badge",
default: "solid",
},
color: {
type: Object.keys(colorPalette),
description: "The color scheme for the badge",
default: "blue",
},
shape: {
type: ["rounded", "square", "pill"],
description: "The shape of the badge",
default: "rounded",
},
size: {
type: ["sm", "md", "lg"],
description: "The size of the badge",
default: "md",
},
maxWidth: {
type: "string",
description: "The maximum width of the badge",
},
animated: {
type: "boolean",
description: "Whether the badge should have animations",
default: false,
},
};
// Types and Interfaces
type BadgeVariants = VariantProps<typeof badgeStyles>;
interface Props extends HTMLAttributes<"span">, BadgeVariants {
content?: string | JSX.Element;
icon?: string | { name: string; class?: string };
iconPosition?: "left" | "right";
class?: string;
removable?: boolean;
variant?: "solid" | "soft" | "outline";
color?: ColorName;
shape?: "rounded" | "square" | "pill";
size?: "sm" | "md" | "lg";
maxWidth?: string;
animated?: boolean;
}
// Component Logic
const {
content,
icon,
iconPosition = "left",
variant = "solid",
color = "blue",
removable = false,
shape = "rounded",
class: className = "",
size = "md",
maxWidth,
animated = false,
} = Astro.props as Props;
// Styles
const badgeStyles = tv({
base: "inline-flex items-center font-medium",
variants: {
variant: {
solid: getDefaultClasses(color, { includeHover: false }),
soft: getSoftClasses(color, { includeHover: false }),
outline: `${getOutlinedClasses(color, { includeHover: false })} border`,
},
shape: {
rounded: "rounded",
square: "",
pill: "rounded-full",
},
size: {
sm: "px-2 py-1 text-xs",
md: "px-2 py-1 text-sm",
lg: "px-3 py-1 text-base",
},
animated: {
true: "transition-all duration-300 ease-in-out hover:scale-105",
false: "",
},
},
defaultVariants: {
variant: "solid",
shape: "rounded",
size: "md",
animated: false,
},
});
const finalIcon = typeof icon === "string" ? { name: icon, class: "" } : icon;
---
<span
class={twMerge(
badgeStyles({ variant, shape, size, animated }),
maxWidth && `max-w-[${maxWidth}] truncate`,
className,
)}
>
{
finalIcon && iconPosition === "left" && (
<Icon
name={finalIcon.name}
class={twMerge(
`mr-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{content && <Fragment set:html={content} />}
<slot />
{
finalIcon && iconPosition === "right" && (
<Icon
name={finalIcon.name}
class={twMerge(
`ml-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{
removable && (
<button
type="button"
class={twMerge(
"ml-1 -mr-1 hover:bg-opacity-10 hover:bg-black focus:outline-none focus:ring-2 focus:ring-offset-2",
`focus:ring-${color}-500 rounded-${shape === "pill" ? "full" : "sm"}`,
)}
aria-label="Remove"
>
<Icon
name="XMarkIcon"
class={twMerge(
size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4",
)}
aria-hidden="true"
/>
</button>
)
}
</span>
<span
class="inline-flex items-center font-medium bg-blue-500 dark:bg-blue-600 text-white dark:text-white border-blue-600 dark:border-blue-500 rounded px-2 py-1 text-xs">
Small
</span>
<span
class="inline-flex items-center font-medium bg-blue-500 dark:bg-blue-600 text-white dark:text-white border-blue-600 dark:border-blue-500 rounded px-2 py-1 text-sm ml-2">
Medium
</span>
<span
class="inline-flex items-center font-medium bg-blue-500 dark:bg-blue-600 text-white dark:text-white border-blue-600 dark:border-blue-500 rounded px-3 py-1 text-base ml-2">
Large
</span>
---
import { Badge } from '@/components/ui/badge';
---
<Badge size="sm">Small</Badge>
<Badge size="md">Medium</Badge>
<Badge size="lg">Large</Badge> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import {
getOutlinedClasses,
type ColorName,
getDefaultClasses,
getSoftClasses,
colorPalette,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
// PropTypes for documentation
export const propTypes = {
content: {
type: "string | JSX.Element",
description: "The content of the badge",
},
icon: {
type: "string | { name: string; class?: string }",
description: "The icon to display in the badge",
},
iconPosition: {
type: ["left", "right"],
description: "The position of the icon",
default: "left",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the badge",
},
removable: {
type: "boolean",
description: "Whether the badge is removable",
default: false,
},
variant: {
type: ["solid", "soft", "outline"],
description: "The variant of the badge",
default: "solid",
},
color: {
type: Object.keys(colorPalette),
description: "The color scheme for the badge",
default: "blue",
},
shape: {
type: ["rounded", "square", "pill"],
description: "The shape of the badge",
default: "rounded",
},
size: {
type: ["sm", "md", "lg"],
description: "The size of the badge",
default: "md",
},
maxWidth: {
type: "string",
description: "The maximum width of the badge",
},
animated: {
type: "boolean",
description: "Whether the badge should have animations",
default: false,
},
};
// Types and Interfaces
type BadgeVariants = VariantProps<typeof badgeStyles>;
interface Props extends HTMLAttributes<"span">, BadgeVariants {
content?: string | JSX.Element;
icon?: string | { name: string; class?: string };
iconPosition?: "left" | "right";
class?: string;
removable?: boolean;
variant?: "solid" | "soft" | "outline";
color?: ColorName;
shape?: "rounded" | "square" | "pill";
size?: "sm" | "md" | "lg";
maxWidth?: string;
animated?: boolean;
}
// Component Logic
const {
content,
icon,
iconPosition = "left",
variant = "solid",
color = "blue",
removable = false,
shape = "rounded",
class: className = "",
size = "md",
maxWidth,
animated = false,
} = Astro.props as Props;
// Styles
const badgeStyles = tv({
base: "inline-flex items-center font-medium",
variants: {
variant: {
solid: getDefaultClasses(color, { includeHover: false }),
soft: getSoftClasses(color, { includeHover: false }),
outline: `${getOutlinedClasses(color, { includeHover: false })} border`,
},
shape: {
rounded: "rounded",
square: "",
pill: "rounded-full",
},
size: {
sm: "px-2 py-1 text-xs",
md: "px-2 py-1 text-sm",
lg: "px-3 py-1 text-base",
},
animated: {
true: "transition-all duration-300 ease-in-out hover:scale-105",
false: "",
},
},
defaultVariants: {
variant: "solid",
shape: "rounded",
size: "md",
animated: false,
},
});
const finalIcon = typeof icon === "string" ? { name: icon, class: "" } : icon;
---
<span
class={twMerge(
badgeStyles({ variant, shape, size, animated }),
maxWidth && `max-w-[${maxWidth}] truncate`,
className,
)}
>
{
finalIcon && iconPosition === "left" && (
<Icon
name={finalIcon.name}
class={twMerge(
`mr-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{content && <Fragment set:html={content} />}
<slot />
{
finalIcon && iconPosition === "right" && (
<Icon
name={finalIcon.name}
class={twMerge(
`ml-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{
removable && (
<button
type="button"
class={twMerge(
"ml-1 -mr-1 hover:bg-opacity-10 hover:bg-black focus:outline-none focus:ring-2 focus:ring-offset-2",
`focus:ring-${color}-500 rounded-${shape === "pill" ? "full" : "sm"}`,
)}
aria-label="Remove"
>
<Icon
name="XMarkIcon"
class={twMerge(
size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4",
)}
aria-hidden="true"
/>
</button>
)
}
</span>
<span
class="inline-flex items-center font-medium bg-blue-500 dark:bg-blue-600 text-white dark:text-white border-blue-600 dark:border-blue-500 rounded px-2 py-1 text-sm">
<span class="inline-block size-5 mr-1 w-4 h-4">
<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 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
clip-rule="evenodd"></path>
</svg>
</span>
Left Icon
</span>
<span
class="inline-flex items-center font-medium bg-blue-500 dark:bg-blue-600 text-white dark:text-white border-blue-600 dark:border-blue-500 rounded px-2 py-1 text-sm ml-2">
Right Icon
<span class="inline-block size-5 ml-1 w-4 h-4">
<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 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
clip-rule="evenodd"></path>
</svg>
</span>
</span>
---
import { Badge } from '@/components/ui/badge';
---
<Badge icon="CheckCircleIcon" iconPosition="left">Left Icon</Badge>
<Badge icon="CheckCircleIcon" iconPosition="right">Right Icon</Badge> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import {
getOutlinedClasses,
type ColorName,
getDefaultClasses,
getSoftClasses,
colorPalette,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
// PropTypes for documentation
export const propTypes = {
content: {
type: "string | JSX.Element",
description: "The content of the badge",
},
icon: {
type: "string | { name: string; class?: string }",
description: "The icon to display in the badge",
},
iconPosition: {
type: ["left", "right"],
description: "The position of the icon",
default: "left",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the badge",
},
removable: {
type: "boolean",
description: "Whether the badge is removable",
default: false,
},
variant: {
type: ["solid", "soft", "outline"],
description: "The variant of the badge",
default: "solid",
},
color: {
type: Object.keys(colorPalette),
description: "The color scheme for the badge",
default: "blue",
},
shape: {
type: ["rounded", "square", "pill"],
description: "The shape of the badge",
default: "rounded",
},
size: {
type: ["sm", "md", "lg"],
description: "The size of the badge",
default: "md",
},
maxWidth: {
type: "string",
description: "The maximum width of the badge",
},
animated: {
type: "boolean",
description: "Whether the badge should have animations",
default: false,
},
};
// Types and Interfaces
type BadgeVariants = VariantProps<typeof badgeStyles>;
interface Props extends HTMLAttributes<"span">, BadgeVariants {
content?: string | JSX.Element;
icon?: string | { name: string; class?: string };
iconPosition?: "left" | "right";
class?: string;
removable?: boolean;
variant?: "solid" | "soft" | "outline";
color?: ColorName;
shape?: "rounded" | "square" | "pill";
size?: "sm" | "md" | "lg";
maxWidth?: string;
animated?: boolean;
}
// Component Logic
const {
content,
icon,
iconPosition = "left",
variant = "solid",
color = "blue",
removable = false,
shape = "rounded",
class: className = "",
size = "md",
maxWidth,
animated = false,
} = Astro.props as Props;
// Styles
const badgeStyles = tv({
base: "inline-flex items-center font-medium",
variants: {
variant: {
solid: getDefaultClasses(color, { includeHover: false }),
soft: getSoftClasses(color, { includeHover: false }),
outline: `${getOutlinedClasses(color, { includeHover: false })} border`,
},
shape: {
rounded: "rounded",
square: "",
pill: "rounded-full",
},
size: {
sm: "px-2 py-1 text-xs",
md: "px-2 py-1 text-sm",
lg: "px-3 py-1 text-base",
},
animated: {
true: "transition-all duration-300 ease-in-out hover:scale-105",
false: "",
},
},
defaultVariants: {
variant: "solid",
shape: "rounded",
size: "md",
animated: false,
},
});
const finalIcon = typeof icon === "string" ? { name: icon, class: "" } : icon;
---
<span
class={twMerge(
badgeStyles({ variant, shape, size, animated }),
maxWidth && `max-w-[${maxWidth}] truncate`,
className,
)}
>
{
finalIcon && iconPosition === "left" && (
<Icon
name={finalIcon.name}
class={twMerge(
`mr-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{content && <Fragment set:html={content} />}
<slot />
{
finalIcon && iconPosition === "right" && (
<Icon
name={finalIcon.name}
class={twMerge(
`ml-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{
removable && (
<button
type="button"
class={twMerge(
"ml-1 -mr-1 hover:bg-opacity-10 hover:bg-black focus:outline-none focus:ring-2 focus:ring-offset-2",
`focus:ring-${color}-500 rounded-${shape === "pill" ? "full" : "sm"}`,
)}
aria-label="Remove"
>
<Icon
name="XMarkIcon"
class={twMerge(
size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4",
)}
aria-hidden="true"
/>
</button>
)
}
</span>
<span
class="inline-flex items-center font-medium bg-blue-500 dark:bg-blue-600 text-white dark:text-white border-blue-600 dark:border-blue-500 rounded px-2 py-1 text-sm">
<p>Removable</p>
<button
type="button"
class="ml-1 -mr-1 hover:bg-opacity-10 hover:bg-black focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 rounded-sm"
aria-label="Remove">
<span class="inline-block size-5 w-4 h-4">
<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.47 5.47a.75.75 0 0 1 1.06 0L12 10.94l5.47-5.47a.75.75 0 1 1 1.06 1.06L13.06 12l5.47 5.47a.75.75 0 1 1-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 0 1-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"></path>
</svg>
</span>
</button>
</span>
---
import { Badge } from '@/components/ui/badge';
---
<Badge removable>
Removable
</Badge> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import {
getOutlinedClasses,
type ColorName,
getDefaultClasses,
getSoftClasses,
colorPalette,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
// PropTypes for documentation
export const propTypes = {
content: {
type: "string | JSX.Element",
description: "The content of the badge",
},
icon: {
type: "string | { name: string; class?: string }",
description: "The icon to display in the badge",
},
iconPosition: {
type: ["left", "right"],
description: "The position of the icon",
default: "left",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the badge",
},
removable: {
type: "boolean",
description: "Whether the badge is removable",
default: false,
},
variant: {
type: ["solid", "soft", "outline"],
description: "The variant of the badge",
default: "solid",
},
color: {
type: Object.keys(colorPalette),
description: "The color scheme for the badge",
default: "blue",
},
shape: {
type: ["rounded", "square", "pill"],
description: "The shape of the badge",
default: "rounded",
},
size: {
type: ["sm", "md", "lg"],
description: "The size of the badge",
default: "md",
},
maxWidth: {
type: "string",
description: "The maximum width of the badge",
},
animated: {
type: "boolean",
description: "Whether the badge should have animations",
default: false,
},
};
// Types and Interfaces
type BadgeVariants = VariantProps<typeof badgeStyles>;
interface Props extends HTMLAttributes<"span">, BadgeVariants {
content?: string | JSX.Element;
icon?: string | { name: string; class?: string };
iconPosition?: "left" | "right";
class?: string;
removable?: boolean;
variant?: "solid" | "soft" | "outline";
color?: ColorName;
shape?: "rounded" | "square" | "pill";
size?: "sm" | "md" | "lg";
maxWidth?: string;
animated?: boolean;
}
// Component Logic
const {
content,
icon,
iconPosition = "left",
variant = "solid",
color = "blue",
removable = false,
shape = "rounded",
class: className = "",
size = "md",
maxWidth,
animated = false,
} = Astro.props as Props;
// Styles
const badgeStyles = tv({
base: "inline-flex items-center font-medium",
variants: {
variant: {
solid: getDefaultClasses(color, { includeHover: false }),
soft: getSoftClasses(color, { includeHover: false }),
outline: `${getOutlinedClasses(color, { includeHover: false })} border`,
},
shape: {
rounded: "rounded",
square: "",
pill: "rounded-full",
},
size: {
sm: "px-2 py-1 text-xs",
md: "px-2 py-1 text-sm",
lg: "px-3 py-1 text-base",
},
animated: {
true: "transition-all duration-300 ease-in-out hover:scale-105",
false: "",
},
},
defaultVariants: {
variant: "solid",
shape: "rounded",
size: "md",
animated: false,
},
});
const finalIcon = typeof icon === "string" ? { name: icon, class: "" } : icon;
---
<span
class={twMerge(
badgeStyles({ variant, shape, size, animated }),
maxWidth && `max-w-[${maxWidth}] truncate`,
className,
)}
>
{
finalIcon && iconPosition === "left" && (
<Icon
name={finalIcon.name}
class={twMerge(
`mr-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{content && <Fragment set:html={content} />}
<slot />
{
finalIcon && iconPosition === "right" && (
<Icon
name={finalIcon.name}
class={twMerge(
`ml-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{
removable && (
<button
type="button"
class={twMerge(
"ml-1 -mr-1 hover:bg-opacity-10 hover:bg-black focus:outline-none focus:ring-2 focus:ring-offset-2",
`focus:ring-${color}-500 rounded-${shape === "pill" ? "full" : "sm"}`,
)}
aria-label="Remove"
>
<Icon
name="XMarkIcon"
class={twMerge(
size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4",
)}
aria-hidden="true"
/>
</button>
)
}
</span>
<span
class="inline-flex items-center font-medium bg-blue-500 dark:bg-blue-600 text-white dark:text-white border-blue-600 dark:border-blue-500 rounded px-2 py-1 text-sm transition-all duration-300 ease-in-out hover:scale-105">
<p>Animated</p>
</span>
---
import { Badge } from '@/components/ui/badge';
---
<Badge animated>
Animated
</Badge> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import {
getOutlinedClasses,
type ColorName,
getDefaultClasses,
getSoftClasses,
colorPalette,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
// PropTypes for documentation
export const propTypes = {
content: {
type: "string | JSX.Element",
description: "The content of the badge",
},
icon: {
type: "string | { name: string; class?: string }",
description: "The icon to display in the badge",
},
iconPosition: {
type: ["left", "right"],
description: "The position of the icon",
default: "left",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the badge",
},
removable: {
type: "boolean",
description: "Whether the badge is removable",
default: false,
},
variant: {
type: ["solid", "soft", "outline"],
description: "The variant of the badge",
default: "solid",
},
color: {
type: Object.keys(colorPalette),
description: "The color scheme for the badge",
default: "blue",
},
shape: {
type: ["rounded", "square", "pill"],
description: "The shape of the badge",
default: "rounded",
},
size: {
type: ["sm", "md", "lg"],
description: "The size of the badge",
default: "md",
},
maxWidth: {
type: "string",
description: "The maximum width of the badge",
},
animated: {
type: "boolean",
description: "Whether the badge should have animations",
default: false,
},
};
// Types and Interfaces
type BadgeVariants = VariantProps<typeof badgeStyles>;
interface Props extends HTMLAttributes<"span">, BadgeVariants {
content?: string | JSX.Element;
icon?: string | { name: string; class?: string };
iconPosition?: "left" | "right";
class?: string;
removable?: boolean;
variant?: "solid" | "soft" | "outline";
color?: ColorName;
shape?: "rounded" | "square" | "pill";
size?: "sm" | "md" | "lg";
maxWidth?: string;
animated?: boolean;
}
// Component Logic
const {
content,
icon,
iconPosition = "left",
variant = "solid",
color = "blue",
removable = false,
shape = "rounded",
class: className = "",
size = "md",
maxWidth,
animated = false,
} = Astro.props as Props;
// Styles
const badgeStyles = tv({
base: "inline-flex items-center font-medium",
variants: {
variant: {
solid: getDefaultClasses(color, { includeHover: false }),
soft: getSoftClasses(color, { includeHover: false }),
outline: `${getOutlinedClasses(color, { includeHover: false })} border`,
},
shape: {
rounded: "rounded",
square: "",
pill: "rounded-full",
},
size: {
sm: "px-2 py-1 text-xs",
md: "px-2 py-1 text-sm",
lg: "px-3 py-1 text-base",
},
animated: {
true: "transition-all duration-300 ease-in-out hover:scale-105",
false: "",
},
},
defaultVariants: {
variant: "solid",
shape: "rounded",
size: "md",
animated: false,
},
});
const finalIcon = typeof icon === "string" ? { name: icon, class: "" } : icon;
---
<span
class={twMerge(
badgeStyles({ variant, shape, size, animated }),
maxWidth && `max-w-[${maxWidth}] truncate`,
className,
)}
>
{
finalIcon && iconPosition === "left" && (
<Icon
name={finalIcon.name}
class={twMerge(
`mr-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{content && <Fragment set:html={content} />}
<slot />
{
finalIcon && iconPosition === "right" && (
<Icon
name={finalIcon.name}
class={twMerge(
`ml-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{
removable && (
<button
type="button"
class={twMerge(
"ml-1 -mr-1 hover:bg-opacity-10 hover:bg-black focus:outline-none focus:ring-2 focus:ring-offset-2",
`focus:ring-${color}-500 rounded-${shape === "pill" ? "full" : "sm"}`,
)}
aria-label="Remove"
>
<Icon
name="XMarkIcon"
class={twMerge(
size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4",
)}
aria-hidden="true"
/>
</button>
)
}
</span>
<span
class="inline-flex items-center font-medium bg-blue-500 dark:bg-blue-600 text-white dark:text-white border-blue-600 dark:border-blue-500 rounded px-2 py-1 text-sm max-w-[100px] truncate">
<p>This is a very long badge text that will be truncated</p>
</span>
---
import { Badge } from '@/components/ui/badge';
---
<Badge maxWidth="100px">
This is a very long badge text that will be truncated
</Badge> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import {
getOutlinedClasses,
type ColorName,
getDefaultClasses,
getSoftClasses,
colorPalette,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
// PropTypes for documentation
export const propTypes = {
content: {
type: "string | JSX.Element",
description: "The content of the badge",
},
icon: {
type: "string | { name: string; class?: string }",
description: "The icon to display in the badge",
},
iconPosition: {
type: ["left", "right"],
description: "The position of the icon",
default: "left",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the badge",
},
removable: {
type: "boolean",
description: "Whether the badge is removable",
default: false,
},
variant: {
type: ["solid", "soft", "outline"],
description: "The variant of the badge",
default: "solid",
},
color: {
type: Object.keys(colorPalette),
description: "The color scheme for the badge",
default: "blue",
},
shape: {
type: ["rounded", "square", "pill"],
description: "The shape of the badge",
default: "rounded",
},
size: {
type: ["sm", "md", "lg"],
description: "The size of the badge",
default: "md",
},
maxWidth: {
type: "string",
description: "The maximum width of the badge",
},
animated: {
type: "boolean",
description: "Whether the badge should have animations",
default: false,
},
};
// Types and Interfaces
type BadgeVariants = VariantProps<typeof badgeStyles>;
interface Props extends HTMLAttributes<"span">, BadgeVariants {
content?: string | JSX.Element;
icon?: string | { name: string; class?: string };
iconPosition?: "left" | "right";
class?: string;
removable?: boolean;
variant?: "solid" | "soft" | "outline";
color?: ColorName;
shape?: "rounded" | "square" | "pill";
size?: "sm" | "md" | "lg";
maxWidth?: string;
animated?: boolean;
}
// Component Logic
const {
content,
icon,
iconPosition = "left",
variant = "solid",
color = "blue",
removable = false,
shape = "rounded",
class: className = "",
size = "md",
maxWidth,
animated = false,
} = Astro.props as Props;
// Styles
const badgeStyles = tv({
base: "inline-flex items-center font-medium",
variants: {
variant: {
solid: getDefaultClasses(color, { includeHover: false }),
soft: getSoftClasses(color, { includeHover: false }),
outline: `${getOutlinedClasses(color, { includeHover: false })} border`,
},
shape: {
rounded: "rounded",
square: "",
pill: "rounded-full",
},
size: {
sm: "px-2 py-1 text-xs",
md: "px-2 py-1 text-sm",
lg: "px-3 py-1 text-base",
},
animated: {
true: "transition-all duration-300 ease-in-out hover:scale-105",
false: "",
},
},
defaultVariants: {
variant: "solid",
shape: "rounded",
size: "md",
animated: false,
},
});
const finalIcon = typeof icon === "string" ? { name: icon, class: "" } : icon;
---
<span
class={twMerge(
badgeStyles({ variant, shape, size, animated }),
maxWidth && `max-w-[${maxWidth}] truncate`,
className,
)}
>
{
finalIcon && iconPosition === "left" && (
<Icon
name={finalIcon.name}
class={twMerge(
`mr-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{content && <Fragment set:html={content} />}
<slot />
{
finalIcon && iconPosition === "right" && (
<Icon
name={finalIcon.name}
class={twMerge(
`ml-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{
removable && (
<button
type="button"
class={twMerge(
"ml-1 -mr-1 hover:bg-opacity-10 hover:bg-black focus:outline-none focus:ring-2 focus:ring-offset-2",
`focus:ring-${color}-500 rounded-${shape === "pill" ? "full" : "sm"}`,
)}
aria-label="Remove"
>
<Icon
name="XMarkIcon"
class={twMerge(
size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4",
)}
aria-hidden="true"
/>
</button>
)
}
</span>
<span
class="inline-flex items-center font-medium bg-slate-500 dark:bg-slate-600 text-white dark:text-white border-slate-600 dark:border-slate-500 rounded px-2 py-1 text-sm">
Slate
</span>
<span
class="inline-flex items-center font-medium bg-gray-500 dark:bg-gray-600 text-white dark:text-white border-gray-600 dark:border-gray-500 rounded px-2 py-1 text-sm ml-2">
Gray
</span>
<span
class="inline-flex items-center font-medium bg-zinc-500 dark:bg-zinc-600 text-white dark:text-white border-zinc-600 dark:border-zinc-500 rounded px-2 py-1 text-sm ml-2">
Zinc
</span>
<span
class="inline-flex items-center font-medium bg-neutral-500 dark:bg-neutral-600 text-white dark:text-white border-neutral-600 dark:border-neutral-500 rounded px-2 py-1 text-sm ml-2">
Neutral
</span>
<span
class="inline-flex items-center font-medium bg-stone-500 dark:bg-stone-600 text-white dark:text-white border-stone-600 dark:border-stone-500 rounded px-2 py-1 text-sm ml-2">
Stone
</span>
<span
class="inline-flex items-center font-medium bg-red-500 dark:bg-red-600 text-white dark:text-white border-red-600 dark:border-red-500 rounded px-2 py-1 text-sm ml-2">
Red
</span>
<span
class="inline-flex items-center font-medium bg-orange-500 dark:bg-orange-600 text-white dark:text-white border-orange-600 dark:border-orange-500 rounded px-2 py-1 text-sm ml-2">
Orange
</span>
<span
class="inline-flex items-center font-medium bg-amber-500 dark:bg-amber-600 text-white dark:text-white border-amber-600 dark:border-amber-500 rounded px-2 py-1 text-sm ml-2">
Amber
</span>
<span
class="inline-flex items-center font-medium bg-yellow-500 dark:bg-yellow-600 text-white dark:text-white border-yellow-600 dark:border-yellow-500 rounded px-2 py-1 text-sm ml-2">
Yellow
</span>
<span
class="inline-flex items-center font-medium bg-lime-500 dark:bg-lime-600 text-white dark:text-white border-lime-600 dark:border-lime-500 rounded px-2 py-1 text-sm ml-2">
Lime
</span>
<span
class="inline-flex items-center font-medium bg-green-500 dark:bg-green-600 text-white dark:text-white border-green-600 dark:border-green-500 rounded px-2 py-1 text-sm ml-2">
Green
</span>
<span
class="inline-flex items-center font-medium bg-emerald-500 dark:bg-emerald-600 text-white dark:text-white border-emerald-600 dark:border-emerald-500 rounded px-2 py-1 text-sm ml-2">
Emerald
</span>
<span
class="inline-flex items-center font-medium bg-teal-500 dark:bg-teal-600 text-white dark:text-white border-teal-600 dark:border-teal-500 rounded px-2 py-1 text-sm ml-2">
Teal
</span>
<span
class="inline-flex items-center font-medium bg-cyan-500 dark:bg-cyan-600 text-white dark:text-white border-cyan-600 dark:border-cyan-500 rounded px-2 py-1 text-sm ml-2">
Cyan
</span>
<span
class="inline-flex items-center font-medium bg-sky-500 dark:bg-sky-600 text-white dark:text-white border-sky-600 dark:border-sky-500 rounded px-2 py-1 text-sm ml-2">
Sky
</span>
<span
class="inline-flex items-center font-medium bg-blue-500 dark:bg-blue-600 text-white dark:text-white border-blue-600 dark:border-blue-500 rounded px-2 py-1 text-sm ml-2">
Blue
</span>
<span
class="inline-flex items-center font-medium bg-indigo-500 dark:bg-indigo-600 text-white dark:text-white border-indigo-600 dark:border-indigo-500 rounded px-2 py-1 text-sm ml-2">
Indigo
</span>
<span
class="inline-flex items-center font-medium bg-violet-500 dark:bg-violet-600 text-white dark:text-white border-violet-600 dark:border-violet-500 rounded px-2 py-1 text-sm ml-2">
Violet
</span>
<span
class="inline-flex items-center font-medium bg-purple-500 dark:bg-purple-600 text-white dark:text-white border-purple-600 dark:border-purple-500 rounded px-2 py-1 text-sm ml-2">
Purple
</span>
<span
class="inline-flex items-center font-medium bg-fuchsia-500 dark:bg-fuchsia-600 text-white dark:text-white border-fuchsia-600 dark:border-fuchsia-500 rounded px-2 py-1 text-sm ml-2">
Fuchsia
</span>
<span
class="inline-flex items-center font-medium bg-pink-500 dark:bg-pink-600 text-white dark:text-white border-pink-600 dark:border-pink-500 rounded px-2 py-1 text-sm ml-2">
Pink
</span>
<span
class="inline-flex items-center font-medium bg-rose-500 dark:bg-rose-600 text-white dark:text-white border-rose-600 dark:border-rose-500 rounded px-2 py-1 text-sm ml-2">
Rose
</span>
---
import { Badge } from '@/components/ui/badge';
---
<Badge color="slate">Slate</Badge>
<Badge color="gray">Gray</Badge>
<Badge color="zinc">Zinc</Badge>
<Badge color="neutral">Neutral</Badge>
<Badge color="stone">Stone</Badge>
<Badge color="red">Red</Badge>
<Badge color="orange">Orange</Badge>
<Badge color="amber">Amber</Badge>
<Badge color="yellow">Yellow</Badge>
<Badge color="lime">Lime</Badge>
<Badge color="green">Green</Badge>
<Badge color="emerald">Emerald</Badge>
<Badge color="teal">Teal</Badge>
<Badge color="cyan">Cyan</Badge>
<Badge color="sky">Sky</Badge>
<Badge color="blue">Blue</Badge>
<Badge color="indigo">Indigo</Badge>
<Badge color="violet">Violet</Badge>
<Badge color="purple">Purple</Badge>
<Badge color="fuchsia">Fuchsia</Badge>
<Badge color="pink">Pink</Badge>
<Badge color="rose">Rose</Badge> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import {
getOutlinedClasses,
type ColorName,
getDefaultClasses,
getSoftClasses,
colorPalette,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
// PropTypes for documentation
export const propTypes = {
content: {
type: "string | JSX.Element",
description: "The content of the badge",
},
icon: {
type: "string | { name: string; class?: string }",
description: "The icon to display in the badge",
},
iconPosition: {
type: ["left", "right"],
description: "The position of the icon",
default: "left",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the badge",
},
removable: {
type: "boolean",
description: "Whether the badge is removable",
default: false,
},
variant: {
type: ["solid", "soft", "outline"],
description: "The variant of the badge",
default: "solid",
},
color: {
type: Object.keys(colorPalette),
description: "The color scheme for the badge",
default: "blue",
},
shape: {
type: ["rounded", "square", "pill"],
description: "The shape of the badge",
default: "rounded",
},
size: {
type: ["sm", "md", "lg"],
description: "The size of the badge",
default: "md",
},
maxWidth: {
type: "string",
description: "The maximum width of the badge",
},
animated: {
type: "boolean",
description: "Whether the badge should have animations",
default: false,
},
};
// Types and Interfaces
type BadgeVariants = VariantProps<typeof badgeStyles>;
interface Props extends HTMLAttributes<"span">, BadgeVariants {
content?: string | JSX.Element;
icon?: string | { name: string; class?: string };
iconPosition?: "left" | "right";
class?: string;
removable?: boolean;
variant?: "solid" | "soft" | "outline";
color?: ColorName;
shape?: "rounded" | "square" | "pill";
size?: "sm" | "md" | "lg";
maxWidth?: string;
animated?: boolean;
}
// Component Logic
const {
content,
icon,
iconPosition = "left",
variant = "solid",
color = "blue",
removable = false,
shape = "rounded",
class: className = "",
size = "md",
maxWidth,
animated = false,
} = Astro.props as Props;
// Styles
const badgeStyles = tv({
base: "inline-flex items-center font-medium",
variants: {
variant: {
solid: getDefaultClasses(color, { includeHover: false }),
soft: getSoftClasses(color, { includeHover: false }),
outline: `${getOutlinedClasses(color, { includeHover: false })} border`,
},
shape: {
rounded: "rounded",
square: "",
pill: "rounded-full",
},
size: {
sm: "px-2 py-1 text-xs",
md: "px-2 py-1 text-sm",
lg: "px-3 py-1 text-base",
},
animated: {
true: "transition-all duration-300 ease-in-out hover:scale-105",
false: "",
},
},
defaultVariants: {
variant: "solid",
shape: "rounded",
size: "md",
animated: false,
},
});
const finalIcon = typeof icon === "string" ? { name: icon, class: "" } : icon;
---
<span
class={twMerge(
badgeStyles({ variant, shape, size, animated }),
maxWidth && `max-w-[${maxWidth}] truncate`,
className,
)}
>
{
finalIcon && iconPosition === "left" && (
<Icon
name={finalIcon.name}
class={twMerge(
`mr-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{content && <Fragment set:html={content} />}
<slot />
{
finalIcon && iconPosition === "right" && (
<Icon
name={finalIcon.name}
class={twMerge(
`ml-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{
removable && (
<button
type="button"
class={twMerge(
"ml-1 -mr-1 hover:bg-opacity-10 hover:bg-black focus:outline-none focus:ring-2 focus:ring-offset-2",
`focus:ring-${color}-500 rounded-${shape === "pill" ? "full" : "sm"}`,
)}
aria-label="Remove"
>
<Icon
name="XMarkIcon"
class={twMerge(
size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4",
)}
aria-hidden="true"
/>
</button>
)
}
</span>
<span
class="inline-flex items-center font-medium bg-green-100 dark:bg-green-800 text-green-700 dark:text-green-100 border-green-200 dark:border-green-600 rounded-full px-3 py-1 text-base transition-all duration-300 ease-in-out hover:scale-105">
<span class="inline-block size-5 mr-1 w-5 h-5">
<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 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
clip-rule="evenodd"></path>
</svg>
</span>
<p>Success</p>
<button
type="button"
class="ml-1 -mr-1 hover:bg-opacity-10 hover:bg-black focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 rounded-full"
aria-label="Remove">
<span class="inline-block size-5 w-5 h-5">
<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.47 5.47a.75.75 0 0 1 1.06 0L12 10.94l5.47-5.47a.75.75 0 1 1 1.06 1.06L13.06 12l5.47 5.47a.75.75 0 1 1-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 0 1-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"></path>
</svg>
</span>
</button>
</span>
---
import { Badge } from '@/components/ui/badge';
---
<Badge
variant="soft"
color="green"
shape="pill"
size="lg"
icon="CheckCircleIcon"
iconPosition="left"
removable
animated
>
Success
</Badge> ---
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import {
getOutlinedClasses,
type ColorName,
getDefaultClasses,
getSoftClasses,
colorPalette,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
// PropTypes for documentation
export const propTypes = {
content: {
type: "string | JSX.Element",
description: "The content of the badge",
},
icon: {
type: "string | { name: string; class?: string }",
description: "The icon to display in the badge",
},
iconPosition: {
type: ["left", "right"],
description: "The position of the icon",
default: "left",
},
class: {
type: "string",
description: "Additional CSS classes to apply to the badge",
},
removable: {
type: "boolean",
description: "Whether the badge is removable",
default: false,
},
variant: {
type: ["solid", "soft", "outline"],
description: "The variant of the badge",
default: "solid",
},
color: {
type: Object.keys(colorPalette),
description: "The color scheme for the badge",
default: "blue",
},
shape: {
type: ["rounded", "square", "pill"],
description: "The shape of the badge",
default: "rounded",
},
size: {
type: ["sm", "md", "lg"],
description: "The size of the badge",
default: "md",
},
maxWidth: {
type: "string",
description: "The maximum width of the badge",
},
animated: {
type: "boolean",
description: "Whether the badge should have animations",
default: false,
},
};
// Types and Interfaces
type BadgeVariants = VariantProps<typeof badgeStyles>;
interface Props extends HTMLAttributes<"span">, BadgeVariants {
content?: string | JSX.Element;
icon?: string | { name: string; class?: string };
iconPosition?: "left" | "right";
class?: string;
removable?: boolean;
variant?: "solid" | "soft" | "outline";
color?: ColorName;
shape?: "rounded" | "square" | "pill";
size?: "sm" | "md" | "lg";
maxWidth?: string;
animated?: boolean;
}
// Component Logic
const {
content,
icon,
iconPosition = "left",
variant = "solid",
color = "blue",
removable = false,
shape = "rounded",
class: className = "",
size = "md",
maxWidth,
animated = false,
} = Astro.props as Props;
// Styles
const badgeStyles = tv({
base: "inline-flex items-center font-medium",
variants: {
variant: {
solid: getDefaultClasses(color, { includeHover: false }),
soft: getSoftClasses(color, { includeHover: false }),
outline: `${getOutlinedClasses(color, { includeHover: false })} border`,
},
shape: {
rounded: "rounded",
square: "",
pill: "rounded-full",
},
size: {
sm: "px-2 py-1 text-xs",
md: "px-2 py-1 text-sm",
lg: "px-3 py-1 text-base",
},
animated: {
true: "transition-all duration-300 ease-in-out hover:scale-105",
false: "",
},
},
defaultVariants: {
variant: "solid",
shape: "rounded",
size: "md",
animated: false,
},
});
const finalIcon = typeof icon === "string" ? { name: icon, class: "" } : icon;
---
<span
class={twMerge(
badgeStyles({ variant, shape, size, animated }),
maxWidth && `max-w-[${maxWidth}] truncate`,
className,
)}
>
{
finalIcon && iconPosition === "left" && (
<Icon
name={finalIcon.name}
class={twMerge(
`mr-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{content && <Fragment set:html={content} />}
<slot />
{
finalIcon && iconPosition === "right" && (
<Icon
name={finalIcon.name}
class={twMerge(
`ml-1 ${size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4"}`,
finalIcon.class,
)}
aria-hidden="true"
/>
)
}
{
removable && (
<button
type="button"
class={twMerge(
"ml-1 -mr-1 hover:bg-opacity-10 hover:bg-black focus:outline-none focus:ring-2 focus:ring-offset-2",
`focus:ring-${color}-500 rounded-${shape === "pill" ? "full" : "sm"}`,
)}
aria-label="Remove"
>
<Icon
name="XMarkIcon"
class={twMerge(
size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4",
)}
aria-hidden="true"
/>
</button>
)
}
</span>
Component Properties
| Property | Type | Default | Description |
|---|---|---|---|
| content | string | JSX.Element | - | The content of the badge |
| icon | string | { name: string; class?: string } | - | The icon to display in the badge |
| iconPosition | left | right | "left" | The position of the icon |
| class | string | - | Additional CSS classes to apply to the badge |
| removable | boolean | false | Whether the badge is removable |
| variant | solid | soft | outline | "solid" | The variant of the badge |
| color | white | slate | gray | zinc | neutral | stone | red | orange | amber | yellow | lime | green | emerald | teal | cyan | sky | blue | indigo | violet | purple | fuchsia | pink | rose | "blue" | The color scheme for the badge |
| shape | rounded | square | pill | "rounded" | The shape of the badge |
| size | sm | md | lg | "md" | The size of the badge |
| maxWidth | string | - | The maximum width of the badge |
| animated | boolean | false | Whether the badge should have animations |