Skip to content
Pebble Pro is here at $49 one timeSee what’s included ↗
Pebble
ComponentsDocsMCPPricing
⌕ Find a component↗Log in
All components 673

Categories

Core inputs & forms46Buttons & actions33Navigation36Overlays & feedback49Data display33Charts & data viz40Cards & layout43Marketing blocks38App shells & pages37AI interfaces40Text & typography effects33Backgrounds & visual effects38Motion & transitions32Numbers & counters32Media34E-commerce333D & interactive36Themed sets40
Start with the essentials.Installation guide ↗Connect your agent ↗
← All componentsData display

Badge

A small label for a status, count or category.

Preview

ProDraftBetaNewPaidPendingOverdue

Invoices

4
  • ID

    Ines Duarte

    INV-1042

    $1,284.50Paid
  • MO

    Malik Osei

    INV-1041

    $640.00Pending
  • PN

    Priya Nair

    INV-1039

    $2,310.75Overdue
  • TR

    Tomás Reyes

    INV-1043

    $96.20Draft

Install

Add the editable source with the shadcn CLI. Your project also needs the shared theme tokens this component uses. Setup and requirements. The Pebble installer is not published yet.

npx shadcn@latest add https://pebbleui.com/r/badge.json

Props

PropTypeDefaultDescription
variant"default" | "secondary" | "outline" | "accent" | "success" | "warning" | "danger""default"Visual style. default is a small lit key, accent is ember, status variants are tinted.
size"sm" | "md""md"Height and text size.
dotbooleanfalseShows a small status dot before the label, coloured by the variant.
asChildbooleanfalseRenders the single child (e.g. a link) with badge styles.
Build notes
A styled `span` (or any element via `asChild`, using Radix `Slot`). Variants are a lookup of token classes; status variants use a 15% tint of the status color, a 30% border and the `*-strong` text token so small text keeps contrast on the tint in both themes. `data-variant` is exposed for styling. Hover dimming applies only when the badge is rendered as a link or button (`[a&]:hover`, `[button&]:hover`), so static badges do not look clickable.

**Pebble (2026-11-03).** Badges became small objects. `default` is a miniature lit key (key gradient, key-edge ring, key shine); `secondary` a raised card pebble (hairline ring, `shadow-raised-xs`, top highlight); `outline` a plain border ring. Status variants keep the `*-strong` text on a 12-15% tint but swap the border for a 30% ring, and a new `accent` (ember) variant marks the new or live item. The new `dot` prop renders an `aria-hidden` dot before the label in the variant's solid colour; with `asChild` the children go through `Slot.Slottable` so the dot lands inside the child element. The new `size` prop adds `sm` (20 px, 11 px text) beside the default `md` (24 px). Interactive badges get a brightness hover and a 0.97 press that reduced motion removes. `tabular-nums` keeps counts from jittering.

<!-- PEBBLE-LICENSE -->
View source

registry/data/badge/badge.tsx

/* PEBBLE-LICENSE
Built by Pebble. Component: badge.

First-party license: MIT

The following first-party terms apply only to original Pebble material.

MIT License

Copyright (c) 2026 Praxys Solutions LLC (Pebble)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
END PEBBLE-LICENSE */

import * as React from "react";
import { Slot } from "radix-ui";

import { cn } from "@/lib/utils";

// Pebble: badges are small objects, not flat chips. `default` is a miniature
// lit key, `secondary` a raised card pebble, `outline` a hairline ring. Status
// variants are tinted wells with a strong-text label and a matching dot, and
// `accent` (ember) is reserved for the new or live thing.
const variants = {
  default:
    "bg-linear-to-b from-key-top to-key-bottom text-primary-foreground ring-1 ring-key-edge shadow-[var(--key-shine)]",
  secondary: "bg-card text-foreground ring-1 ring-hairline shadow-raised-xs inset-shadow-highlight",
  outline: "bg-transparent text-foreground ring-1 ring-border",
  accent: "bg-accent/12 text-accent-strong ring-1 ring-accent/30",
  success: "bg-success/12 text-success-strong ring-1 ring-success/30",
  warning: "bg-warning/15 text-warning-strong ring-1 ring-warning/35",
  danger: "bg-danger/12 text-danger-strong ring-1 ring-danger/30",
} as const;

const dots = {
  default: "bg-primary-foreground/80",
  secondary: "bg-muted-foreground",
  outline: "bg-muted-foreground",
  accent: "bg-accent",
  success: "bg-success",
  warning: "bg-warning",
  danger: "bg-danger",
} as const;

const sizes = {
  sm: "h-5 gap-1 px-1.5 text-[11px]",
  md: "h-6 gap-1.5 px-2 text-xs",
} as const;

export type BadgeProps = React.ComponentProps<"span"> & {
  variant?: keyof typeof variants;
  size?: keyof typeof sizes;
  /** Shows a small status dot before the label, coloured by the variant. */
  dot?: boolean;
  asChild?: boolean;
};

export function Badge({
  variant = "default",
  size = "md",
  dot = false,
  asChild = false,
  className,
  children,
  ...props
}: BadgeProps) {
  const Comp = asChild ? Slot.Root : "span";
  const marker = dot ? (
    <span
      aria-hidden
      data-slot="badge-dot"
      className={cn("size-1.5 shrink-0 rounded-full", dots[variant])}
    />
  ) : null;
  return (
    <Comp
      data-slot="badge"
      data-variant={variant}
      className={cn(
        "inline-flex w-fit shrink-0 items-center rounded-full font-medium whitespace-nowrap tabular-nums select-none [&_svg]:size-3 [&_svg]:shrink-0",
        "transition-[background-color,box-shadow,filter,scale] duration-(--motion-fast) ease-enter motion-reduce:transition-none",
        "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none",
        // Interactive badges (links, buttons) get a whisper of hover and a felt press.
        "[a&]:cursor-pointer [a&]:hover:brightness-[0.97] dark:[a&]:hover:brightness-110 [button&]:cursor-pointer [button&]:hover:brightness-[0.97] dark:[button&]:hover:brightness-110",
        "[a&]:active:scale-[0.97] motion-reduce:[a&]:active:scale-100 [button&]:active:scale-[0.97] motion-reduce:[button&]:active:scale-100",
        variants[variant],
        sizes[size],
        className,
      )}
      {...props}
    >
      {marker}
      {asChild ? <Slot.Slottable>{children}</Slot.Slottable> : children}
    </Comp>
  );
}
Pebble

Small details.
Better interfaces.

React components to explore, make your own, and build on.

Library

  • Components
  • Installation
  • Pricing

Resources

  • For agents
  • About
  • Support

Legal

  • License
  • Terms
  • Privacy
  • Final sale

Built by Pebble

A product of Praxys Solutions LLC ↗

Pebble Pro is live

$49 one time, lifetime updates ↗