Display
Badge
Indicador de estado con variantes
Preview
Nuevo
size
variant
color
children
Code
import css from "./Badge.module.css";
type Size = "xs" | "sm" | "md";
type Color = "text-tertiary" | "accent";
interface ResponsiveSize {
base: Size;
mobile?: Size;
tablet?: Size;
desktop?: Size;
wide?: Size;
}
interface BadgeProps {
size: Size | ResponsiveSize;
variant: "outline" | "solid" | "soft";
color: Color;
children: React.ReactNode;
}
function getSizeClasses(size: Size | ResponsiveSize): string {
if (typeof size === "string") {
return css[size];
}
const classes = [css[`${size.base}_base`]];
if (size.mobile) classes.push(css[`${size.mobile}_mobile`]);
if (size.tablet) classes.push(css[`${size.tablet}_tablet`]);
if (size.desktop) classes.push(css[`${size.desktop}_desktop`]);
if (size.wide) classes.push(css[`${size.wide}_wide`]);
return classes.join(" ");
}
export default function Badge({ size, variant, color, children }: BadgeProps) {
return (
<div
className={`
${css.badge}
${getSizeClasses(size)}
${css[variant]}
`}
style={
{ "--badge-color": `var(--color-${color})` } as React.CSSProperties
}
>
<p>{children}</p>
</div>
);
}
.badge {
display: inline-flex;
align-items: center;
justify-content: center;
border: var(--border-surface) solid;
border-radius: var(--radius-8);
font-family: var(--font-body);
font-weight: var(--weight-600);
font-variant-numeric: tabular-nums;
&.xs {
height: var(--size-24);
padding: 0 var(--space-8);
font-size: var(--text-12);
}
&.sm {
height: var(--size-28);
padding: 0 var(--space-8);
font-size: var(--text-14);
}
&.md {
height: var(--size-32);
padding: 0 var(--space-12);
font-size: var(--text-14);
}
&.xs_base {
height: var(--size-24);
padding: 0 var(--space-8);
font-size: var(--text-12);
}
&.sm_base {
height: var(--size-28);
padding: 0 var(--space-8);
font-size: var(--text-14);
}
&.md_base {
height: var(--size-32);
padding: 0 var(--space-12);
font-size: var(--text-14);
}
@media (min-width: 480px) {
&.xs_mobile {
height: var(--size-24);
padding: 0 var(--space-8);
font-size: var(--text-12);
}
&.sm_mobile {
height: var(--size-28);
padding: 0 var(--space-8);
font-size: var(--text-14);
}
&.md_mobile {
height: var(--size-32);
padding: 0 var(--space-12);
font-size: var(--text-14);
}
}
@media (min-width: 768px) {
&.xs_tablet {
height: var(--size-24);
padding: 0 var(--space-8);
font-size: var(--text-12);
}
&.sm_tablet {
height: var(--size-28);
padding: 0 var(--space-8);
font-size: var(--text-14);
}
&.md_tablet {
height: var(--size-32);
padding: 0 var(--space-12);
font-size: var(--text-14);
}
}
@media (min-width: 1024px) {
&.xs_desktop {
height: var(--size-24);
padding: 0 var(--space-8);
font-size: var(--text-12);
}
&.sm_desktop {
height: var(--size-28);
padding: 0 var(--space-8);
font-size: var(--text-14);
}
&.md_desktop {
height: var(--size-32);
padding: 0 var(--space-12);
font-size: var(--text-14);
}
}
@media (min-width: 1440px) {
&.xs_wide {
height: var(--size-24);
padding: 0 var(--space-8);
font-size: var(--text-12);
}
&.sm_wide {
height: var(--size-28);
padding: 0 var(--space-8);
font-size: var(--text-14);
}
&.md_wide {
height: var(--size-32);
padding: 0 var(--space-12);
font-size: var(--text-14);
}
}
&.outline {
background: none;
color: var(--badge-color);
border-color: var(--badge-color);
}
&.solid {
background: var(--badge-color);
color: var(--color-bg);
border-color: var(--badge-color);
}
&.soft {
background: color-mix(in srgb, var(--badge-color) 12%, transparent);
color: var(--badge-color);
border-color: transparent;
}
}
<script setup lang="ts">
import { computed } from "vue";
type Size = "xs" | "sm" | "md";
type Color = "text-tertiary" | "accent";
interface ResponsiveSize {
base: Size;
mobile?: Size;
tablet?: Size;
desktop?: Size;
wide?: Size;
}
interface BadgeProps {
size: Size | ResponsiveSize;
variant: "outline" | "solid" | "soft";
color: Color;
}
const { size } = defineProps<BadgeProps>();
const sizeClasses = computed(() => {
if (typeof size === "string") {
return size;
}
const classes = [`${size.base}_base`];
if (size.mobile) classes.push(`${size.mobile}_mobile`);
if (size.tablet) classes.push(`${size.tablet}_tablet`);
if (size.desktop) classes.push(`${size.desktop}_desktop`);
if (size.wide) classes.push(`${size.wide}_wide`);
return classes.join(" ");
});
</script>
<template>
<div
class="badge"
:class="[sizeClasses, variant]"
:style="{ '--badge-color': `var(--color-${color})` }"
>
<p><slot></slot></p>
</div>
</template>
<style scoped>
.badge {
display: inline-flex;
align-items: center;
justify-content: center;
border: var(--border-surface) solid;
border-radius: var(--radius-8);
font-family: var(--font-body);
font-weight: var(--weight-600);
font-variant-numeric: tabular-nums;
&.xs {
height: var(--size-24);
padding: 0 var(--space-8);
font-size: var(--text-12);
}
&.sm {
height: var(--size-28);
padding: 0 var(--space-8);
font-size: var(--text-14);
}
&.md {
height: var(--size-32);
padding: 0 var(--space-12);
font-size: var(--text-14);
}
&.xs_base {
height: var(--size-24);
padding: 0 var(--space-8);
font-size: var(--text-12);
}
&.sm_base {
height: var(--size-28);
padding: 0 var(--space-8);
font-size: var(--text-14);
}
&.md_base {
height: var(--size-32);
padding: 0 var(--space-12);
font-size: var(--text-14);
}
@media (min-width: 480px) {
&.xs_mobile {
height: var(--size-24);
padding: 0 var(--space-8);
font-size: var(--text-12);
}
&.sm_mobile {
height: var(--size-28);
padding: 0 var(--space-8);
font-size: var(--text-14);
}
&.md_mobile {
height: var(--size-32);
padding: 0 var(--space-12);
font-size: var(--text-14);
}
}
@media (min-width: 768px) {
&.xs_tablet {
height: var(--size-24);
padding: 0 var(--space-8);
font-size: var(--text-12);
}
&.sm_tablet {
height: var(--size-28);
padding: 0 var(--space-8);
font-size: var(--text-14);
}
&.md_tablet {
height: var(--size-32);
padding: 0 var(--space-12);
font-size: var(--text-14);
}
}
@media (min-width: 1024px) {
&.xs_desktop {
height: var(--size-24);
padding: 0 var(--space-8);
font-size: var(--text-12);
}
&.sm_desktop {
height: var(--size-28);
padding: 0 var(--space-8);
font-size: var(--text-14);
}
&.md_desktop {
height: var(--size-32);
padding: 0 var(--space-12);
font-size: var(--text-14);
}
}
@media (min-width: 1440px) {
&.xs_wide {
height: var(--size-24);
padding: 0 var(--space-8);
font-size: var(--text-12);
}
&.sm_wide {
height: var(--size-28);
padding: 0 var(--space-8);
font-size: var(--text-14);
}
&.md_wide {
height: var(--size-32);
padding: 0 var(--space-12);
font-size: var(--text-14);
}
}
&.outline {
background: none;
color: var(--badge-color);
border-color: var(--badge-color);
}
&.solid {
background: var(--badge-color);
color: var(--color-bg);
border-color: var(--badge-color);
}
&.soft {
background: color-mix(in srgb, var(--badge-color) 12%, transparent);
color: var(--badge-color);
border-color: transparent;
}
}
</style>
Props
| Prop | Type | Required | Description |
|---|---|---|---|
| size | "xs" | "sm" | "md" | ResponsiveSize | required | Tamaño de la insignia (altura, tipografía y padding). Fijo o responsive. |
| variant | "outline" | "solid" | "soft" | required | Estilo visual: contorno, relleno sólido o relleno tenue. |
| color | "text-tertiary" | "accent" | required | Token del sistema que tiñe la insignia. |
| children | React.ReactNode | required | Contenido de la insignia. |