Badge
Displays a badge or a component that looks like a badge.
- v1.0.0
- SSR
- Zoneless
- 0.8 kB gzip
import { Component } from '@angular/core';
import { Badge } from '@/ui/badge/badge';
/** The four main Badge variants, then badges used as counts. */
@Component({
selector: 'example-badge-demo',
imports: [Badge],
template: `
<div class="flex flex-col items-center gap-2">
<div class="flex w-full flex-wrap gap-2">
<span ndBadge>Badge</span>
<span ndBadge variant="secondary">Secondary</span>
<span ndBadge variant="destructive">Destructive</span>
<span ndBadge variant="outline">Outline</span>
</div>
<div class="flex w-full flex-wrap gap-2">
<span
ndBadge
class="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
>8</span
>
<span
ndBadge
variant="destructive"
class="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
>99</span
>
<span
ndBadge
variant="outline"
class="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
>20+</span
>
</div>
</div>
`,
})
export class BadgeDemoExample {}
A badge marks something with a short status, count or label, such as
"Beta", "3 new" or a version. ndBadge only styles its host, so a badge is
as accessible as the element it's on: plain text in a span, a link in an
a, an item in a list. Six variants set the emphasis, from default to
link.
Installation
npx @ndop/cli add badgepnpm dlx @ndop/cli add badgeyarn dlx @ndop/cli add badgebunx @ndop/cli add badgeInstall the dependencies
npm install @ndop/corepnpm add @ndop/coreyarn add @ndop/corebun add @ndop/coreCopy the code into your project
At the paths your
ndop.jsonaliases point to; these use the defaults.src/app/ui/badge/badge.tsimport { computed, Directive, input } from '@angular/core'; import { cn, variants, type VariantOf } from '@/lib/utils'; /** * Class table for {@link Badge}; also usable to style other elements like * a badge. */ export const badgeVariants = variants({ base: 'inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3', variants: { variant: { default: 'bg-primary text-primary-foreground [a&]:hover:bg-primary/90', secondary: 'bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', destructive: 'bg-destructive text-destructive-foreground focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90', outline: 'border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', ghost: '[a&]:hover:bg-accent [a&]:hover:text-accent-foreground', link: 'text-primary underline-offset-4 [a&]:hover:underline', }, }, defaults: { variant: 'default' }, }); /** Visual style of a {@link Badge}. */ export type BadgeVariant = VariantOf<typeof badgeVariants, 'variant'>; /** * Styled badge: a short status, count or label. It only styles its host, * so the host keeps its own semantics: a `span` for text, an `a` when it * links somewhere, an `li` in a list of badges. * * @example * ```html * <span ndBadge variant="secondary">Beta</span> * <a ndBadge variant="outline" href="/changelog">v1.2</a> * ``` */ @Directive({ selector: '[ndBadge]', host: { 'data-slot': 'badge', '[attr.data-variant]': 'variant()', '[class]': 'classes()', }, }) export class Badge { /** Visual style. */ readonly variant = input<BadgeVariant>('default'); /** Extra classes, merged last so they win. */ readonly class = input(''); protected readonly classes = computed(() => cn(badgeVariants({ variant: this.variant() }), this.class()), ); }src/app/lib/utils.ts/** * Class helpers used by every styled item. They come from `@ndop/core`; * this file is yours, so it is the place to customize them, for example by * wrapping `cn` with an extended tailwind-merge configuration for your own * utilities. * * @packageDocumentation */ export { cn, variants, type ClassValue, type VariantOf } from '@ndop/core';Update the import paths to match your project
The files import each other through the
@/uiand@/libaliases.
Usage
import { Badge } from '@/ui/badge/badge';<div class="flex flex-col items-center gap-2">
<div class="flex w-full flex-wrap gap-2">
<span ndBadge>Badge</span>
<span ndBadge variant="secondary">Secondary</span>
<span ndBadge variant="destructive">Destructive</span>
<span ndBadge variant="outline">Outline</span>
</div>
<div class="flex w-full flex-wrap gap-2">
<span
ndBadge
class="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
>8</span
>
<span
ndBadge
variant="destructive"
class="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
>99</span
>
<span
ndBadge
variant="outline"
class="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
>20+</span
>
</div>
</div>Examples
Badge link
A badge that links somewhere, with a hover style.
import { Component } from '@angular/core';
import { Badge } from '@/ui/badge/badge';
/** A badge that links somewhere, with a hover style. */
@Component({
selector: 'example-badge-link',
imports: [Badge],
template: `<a ndBadge variant="outline" href="#changelog">Changelog</a>`,
})
export class BadgeLinkExample {}
Badge variants
Every Badge variant side by side.
import { Component } from '@angular/core';
import { Badge } from '@/ui/badge/badge';
/** Every Badge variant side by side. */
@Component({
selector: 'example-badge-variants',
imports: [Badge],
template: `
<div class="flex flex-wrap gap-2">
<span ndBadge>Default</span>
<span ndBadge variant="secondary">Secondary</span>
<span ndBadge variant="destructive">Destructive</span>
<span ndBadge variant="outline">Outline</span>
<span ndBadge variant="ghost">Ghost</span>
<a ndBadge variant="link" href="#changelog">Link</a>
</div>
`,
})
export class BadgeVariantsExample {}
Accessibility
Badge has no keyboard interaction or focus handling of its own; it keeps the semantics of the element it's on.
API reference
Badge
[ndBadge]Styled badge: a short status, count or label. It only styles its host,
so the host keeps its own semantics: a span for text, an a when it
links somewhere, an li in a list of badges.
| Badge binding | Type | Default |
|---|---|---|
[variant]Visual style. | 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' | 'default' |
[class]Extra classes, merged last so they win. | string | '' |
| Data attribute | Values |
|---|---|
data-slot | badge |
data-variant | — |
Agent notes
Do
- Keep the text to a word or two, or a number.
- Put
ndBadgeon anawith anhrefwhen the badge leads somewhere; it then gets a hover style. - When a badge's meaning depends on its color, also say it in the text, such as "Failed" rather than a red dot.
- Give a group of badges a list (
ulwithli[ndBadge]) and a label when they belong together.
Don't
- Don't put a click handler on a
span[ndBadge]; use a button for actions so keyboard and screen reader users can operate it. - Don't use a badge for long text or sentences; use an alert or plain text.