shadimage
glass-ui
componentbadgesource: ok · hit

Badge

Displays a badge or a component that looks like a badge with glassy variant.

npx shadcn add "https://glass-ui.crenspire.com/r/badge.json"
free

Visual style

glass-blur

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Files6 · source cached 10/14/2026, 5:34:07 PM

lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}
lib/liquid-glass/material.ts
import type * as React from "react"
import { cn } from "@/lib/utils"

/* Liquid glass material: turns a role + material into the CSS classes (see the .lg layer in
   app/globals.css) and the data-glass attributes the WebGL engine picks up. */

export type Material = "regular" | "clear" | "tinted"
export type GlassRole = "surface" | "sheet" | "bar" | "control" | "knob" | "inline" | "overlay"
export interface GlassOverride { tint?: string; frost?: number }
export interface LiquidProps {
  /** Glass material. Default `regular`. */
  material?: Material
  /** Tint colour (any CSS colour). Defaults to the primary colour for `tinted`. */
  tint?: string
  /** Per-instance override. */
  glass?: GlassOverride
  /** Draw with the WebGL engine when a LiquidGlassProvider is present. WebGL glass sits behind
      the page, so only elements resting directly on the backdrop should use it. Defaults to
      true for in-flow roles and false for sheets; overlays and inline items never use it. */
  webgl?: boolean
}

/** `data-*` attributes for the WebGL engine. */
export type DataAttrs = { [K in `data-${string}`]?: string }

const PRESET: Record<GlassRole, string | null> = {
  surface: "card", sheet: "sheet", bar: "bar", control: "button", knob: "knob", inline: null, overlay: null,
}
// Roles whose elements usually float over page content default to CSS-only.
const WEBGL_DEFAULT: Record<GlassRole, boolean> = {
  surface: true, sheet: false, bar: true, control: true, knob: true, inline: false, overlay: false,
}
const PARAMS: Record<Material, { frost: number; tint: number }> = {
  regular: { frost: 1, tint: 0.2 },
  clear: { frost: 0.1, tint: 0 },
  tinted: { frost: 0.6, tint: 0.35 },
}
export function liquidGlass({ role, material = "regular", tint, glass, webgl = WEBGL_DEFAULT[role] }: LiquidProps & { role: GlassRole }): {
  className: string; style: React.CSSProperties; attrs: DataAttrs
} {
  const p = PARAMS[material]
  const color = glass?.tint ?? tint
  const frost = glass?.frost ?? p.frost
  const style: Record<string, string> = {}
  if (color) {
    style["--lg-tint"] = color
    if (material !== "tinted") style["--lg-tint-amt"] = "18%"
  }
  const preset = webgl ? PRESET[role] : null
  const attrs: DataAttrs = preset
    ? { "data-glass": preset, "data-frost": String(frost), "data-tint": String(p.tint) }
    : {}
  return { className: `lg lg-${material} lg-${role}`, style: style as React.CSSProperties, attrs }
}

/** Spread onto an element: material classes/attrs merged with the element's own className and style. */
export function liquidProps(
  opts: LiquidProps & { role: GlassRole },
  own: { className?: string; style?: React.CSSProperties } = {}
): { className: string; style: React.CSSProperties } & DataAttrs {
  const r = liquidGlass(opts)
  return { ...r.attrs, className: cn(r.className, own.className), style: { ...r.style, ...own.style } }
}

/** A component's `variant` (default `liquid`) together with its material (default `regular`). */
export function resolveVariant<V extends string>(variant: V | undefined, material: Material | undefined) {
  return { variant: (variant ?? "liquid") as V | "liquid", material: material ?? "regular" }
}
lib/hover-effects.ts
import { cva } from "class-variance-authority"

/**
 * Shared hover effect variants for all Glass UI components
 * 
 * Effects:
 * - none: No hover effect
 * - glow: Soft white rim bloom that intensifies on hover
 * - shimmer: Moving shimmer effect across the component
 * - ripple: Ripple effect that scales outward on hover
 * - lift: Subtle lift effect with shadow increase
 * - scale: Scale up slightly on hover
 */
export const hoverEffects = cva(
  "transition-all duration-300",
  {
    variants: {
      hover: {
        none: "",
        glow: "shadow-[0_0_0_1px_rgba(255,255,255,0.35),0_0_28px_-4px_rgba(255,255,255,0.45)] hover:shadow-[0_0_0_1px_rgba(255,255,255,0.5),0_0_36px_-4px_rgba(255,255,255,0.6)]",
        shimmer: "relative overflow-hidden before:absolute before:inset-0 before:-translate-x-full before:bg-gradient-to-r before:from-transparent before:via-white/20 before:to-transparent hover:before:translate-x-full before:transition-transform before:duration-1000",
        ripple: "relative overflow-hidden after:absolute after:inset-0 after:scale-0 after:rounded-full after:bg-white/30 after:transition-transform after:duration-500 hover:after:scale-150",
        lift: "hover:-translate-y-1 hover:shadow-lg",
        scale: "hover:scale-105",
      },
    },
    defaultVariants: {
      hover: "none",
    },
  }
)

export type HoverEffect = "none" | "glow" | "shimmer" | "ripple" | "lift" | "scale"

app/globals.css
@import "tailwindcss";
@import "tw-animate-css";

/* Design specs and plans quote class names; they are not UI source. */
@source not "../docs/superpowers";

@custom-variant dark (&:is(.dark *));

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: var(--font-geist-sans);
  --font-mono: var(--font-geist-mono);
  --color-sidebar-ring: var(--sidebar-ring);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar: var(--sidebar);
  --color-chart-5: var(--chart-5);
  --color-chart-4: var(--chart-4);
  --color-chart-3: var(--chart-3);
  --color-chart-2: var(--chart-2);
  --color-chart-1: var(--chart-1);
  --color-ring: var(--ring);
  --color-input: var(--input);
  --color-border: var(--border);
  --color-destructive: var(--destructive);
  --color-accent-foreground: var(--accent-foreground);
  --color-accent: var(--accent);
  --color-muted-foreground: var(--muted-foreground);
  --color-muted: var(--muted);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-secondary: var(--secondary);
  --color-primary-foreground: var(--primary-foreground);
  --color-primary: var(--primary);
  --color-popover-foreground: var(--popover-foreground);
  --color-popover: var(--popover);
  --color-card-foreground: var(--card-foreground);
  --color-card: var(--card);
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
}

:root {
  --radius: 1.25rem;
  /* Base colors - Light mode with dark text */
  --background: transparent;
  --foreground: oklch(0.15 0 0); /* Near black - dark text for light mode */
  --card: transparent;
  --card-foreground: oklch(0.15 0 0); /* Near black - dark text */
  --popover: transparent;
  --popover-foreground: oklch(0.15 0 0); /* Near black - dark text */
  
  /* Primary colors - Light mode */
  --primary: oklch(0.5 0.2 250); /* Blue */
  --primary-foreground: oklch(0.98 0 0); /* White text on primary */
  
  /* Secondary colors - Light mode */
  --secondary: oklch(0.96 0 0); /* Very light gray */
  --secondary-foreground: oklch(0.15 0 0); /* Dark text */
  
  /* Muted colors - Light mode */
  --muted: oklch(0.85 0 0); /* Dark gray - darker shade to avoid glass effect override */
  --muted-foreground: oklch(0.1 0 0); /* Medium gray text */
  
  /* Accent colors - Light mode */
  --accent: oklch(0.96 0 0); /* Very light gray */
  --accent-foreground: oklch(0.15 0 0); /* Dark text */
  
  /* Destructive colors - WCAG AA compliant (4.5:1 contrast) */
  --destructive: oklch(0.55 0.22 25); /* Red - 4.5:1 with white */
  --destructive-foreground: oklch(0.98 0 0); /* White text on red */
  
  /* Border and input - WCAG AA compliant (3:1 for UI components) */
  --border: oklch(0.88 0 0); /* Light gray - 3:1 contrast */
  --input: oklch(0.88 0 0); /* Light gray - 3:1 contrast */
  --ring: oklch(0.5 0.2 250); /* Blue ring - 3:1 contrast */
  
  /* Chart colors - WCAG AA compliant */
  --chart-1: oklch(0.6 0.2 250); /* Blue */
  --chart-2: oklch(0.55 0.15 150); /* Green */
  --chart-3: oklch(0.5 0.2 50); /* Yellow */
  --chart-4: oklch(0.65 0.2 300); /* Purple */
  --chart-5: oklch(0.6 0.2 20); /* Orange */
  
  /* Sidebar colors - Light mode */
  --sidebar: oklch(0.98 0 0); /* Near white */
  --sidebar-foreground: oklch(0.15 0 0); /* Dark text */
  --sidebar-primary: oklch(0.5 0.2 250); /* Blue */
  --sidebar-primary-foreground: oklch(0.98 0 0); /* White text on primary */
  --sidebar-accent: oklch(0.96 0 0); /* Very light gray */
  --sidebar-accent-foreground: oklch(0.15 0 0); /* Dark text */
  --sidebar-border: oklch(0.88 0 0); /* Light gray - 3:1 contrast */
  --sidebar-ring: oklch(0.5 0.2 250); /* Blue ring - 3:1 contrast */
}

.dark {
  /* Base colors - Apple-style white text */
  --background: transparent;
  --foreground: oklch(1 0 0); /* White - Apple-style */
  --card: transparent;
  --card-foreground: oklch(1 0 0); /* White - Apple-style */
  --popover: transparent;
  --popover-foreground: oklch(1 0 0); /* White - Apple-style */
  
  /* Primary colors - Apple-style white text */
  --primary: oklch(0.7 0.15 250); /* Light blue */
  --primary-foreground: oklch(1 0 0); /* White - Apple-style */
  
  /* Secondary colors - Apple-style white text */
  --secondary: oklch(0.25 0 0); /* Dark gray */
  --secondary-foreground: oklch(1 0 0); /* White - Apple-style */
  
  /* Muted colors - Apple-style white text */
  --muted: oklch(0.18 0 0); /* Darker gray - darker shade to avoid glass effect override */
  --muted-foreground: oklch(1 0 0); /* White - Apple-style */
  
  /* Accent colors - Apple-style white text */
  --accent: oklch(0.25 0 0); /* Dark gray */
  --accent-foreground: oklch(1 0 0); /* White - Apple-style */
  
  /* Destructive colors - WCAG AA compliant (4.5:1 contrast) */
  --destructive: oklch(0.65 0.22 25); /* Light red - 4.5:1 with dark background */
  --destructive-foreground: oklch(0.12 0 0); /* Near black text on red */
  
  /* Border and input - WCAG AA compliant (3:1 for UI components) */
  --border: oklch(0.3 0 0); /* Medium gray - 3:1 contrast */
  --input: oklch(0.25 0 0); /* Dark gray - 3:1 contrast */
  --ring: oklch(0.6 0.2 250); /* Light blue ring - 3:1 contrast */
  
  /* Chart colors - WCAG AA compliant */
  --chart-1: oklch(0.65 0.2 250); /* Light blue */
  --chart-2: oklch(0.7 0.15 150); /* Light green */
  --chart-3: oklch(0.75 0.2 50); /* Light yellow */
  --chart-4: oklch(0.7 0.2 300); /* Light purple */
  --chart-5: oklch(0.68 0.2 20); /* Light orange */
  
  /* Sidebar colors - Apple-style white text */
  --sidebar: oklch(0.18 0 0); /* Dark gray */
  --sidebar-foreground: oklch(1 0 0); /* White - Apple-style */
  --sidebar-primary: oklch(0.7 0.15 250); /* Light blue */
  --sidebar-primary-foreground: oklch(1 0 0); /* White - Apple-style */
  --sidebar-accent: oklch(0.25 0 0); /* Dark gray */
  --sidebar-accent-foreground: oklch(1 0 0); /* White - Apple-style */
  --sidebar-border: oklch(0.3 0 0); /* Medium gray - 3:1 contrast */
  --sidebar-ring: oklch(0.6 0.2 250); /* Light blue ring - 3:1 contrast */
}

@layer base {
  * {
    @apply border-border outline-ring/50;
  }
body {
  @apply text-foreground;
  background-color: transparent;
  }
}

@layer utilities {
  /* Ensure react-day-picker doesn't override the liquid glass */
  .lg[class*="rdp"] {
    background-color: transparent !important;
  }
  
  .lg .rdp {
    background-color: transparent !important;
  }
  
  .lg .rdp-month {
    background-color: transparent !important;
  }
}

/* Liquid glass material. Classes come from liquidGlass() in lib/liquid-glass/material.ts.
   The rim is a gradient border-box under a padding-box wash, so no pseudo-elements are used. */
@layer components {
  :root {
    --lg-rim-hi: rgba(255, 255, 255, 0.8);
    --lg-rim-lo: rgba(255, 255, 255, 0.18);
    --lg-highlight: rgba(255, 255, 255, 0.6);
    --lg-drop: rgba(15, 23, 42, 0.14);
  }
  .dark {
    --lg-rim-hi: rgba(255, 255, 255, 0.34);
    --lg-rim-lo: rgba(255, 255, 255, 0.06);
    --lg-highlight: rgba(255, 255, 255, 0.16);
    --lg-drop: rgba(0, 0, 0, 0.45);
  }
  .lg {
    --lg-tint: transparent;
    --lg-tint-amt: 0%;
    --lg-wash: color-mix(in oklab, var(--lg-tint) var(--lg-tint-amt), var(--lg-base));
    color: var(--foreground);
    border: 1px solid transparent;
    background:
      linear-gradient(var(--lg-wash), var(--lg-wash)) padding-box,
      linear-gradient(135deg, var(--lg-rim-hi), var(--lg-rim-lo) 35%, var(--lg-rim-lo) 65%, var(--lg-rim-hi)) border-box;
    backdrop-filter: blur(var(--lg-blur)) saturate(var(--lg-sat));
    -webkit-backdrop-filter: blur(var(--lg-blur)) saturate(var(--lg-sat));
    box-shadow: inset 0 1px 0 var(--lg-highlight), 0 1px 2px var(--lg-drop), 0 12px 32px -14px var(--lg-drop);
  }

  .lg-regular { --lg-base: rgba(255, 255, 255, 0.42); --lg-blur: 16px; --lg-sat: 180%; }
  .dark .lg-regular { --lg-base: rgba(255, 255, 255, 0.07); }
  .lg-clear { --lg-base: rgba(255, 255, 255, 0.1); --lg-blur: 2px; --lg-sat: 150%; }
  .dark .lg-clear { --lg-base: rgba(255, 255, 255, 0.04); }
  .lg-tinted { --lg-tint: var(--primary); --lg-tint-amt: 55%; --lg-base: rgba(255, 255, 255, 0.3); --lg-blur: 12px; --lg-sat: 180%; color: var(--primary-foreground); }
  .dark .lg-tinted { --lg-base: rgba(30, 32, 40, 0.35); }

  .lg-surface { border-radius: 28px; }
  .lg-sheet { border-radius: 34px; }
  .lg-bar, .lg-control, .lg-knob, .lg-inline { border-radius: 9999px; }
  .lg-inline { box-shadow: inset 0 1px 0 var(--lg-highlight); }
  .lg-overlay { border-radius: 20px; }
  .lg-overlay.lg-regular { --lg-base: rgba(255, 255, 255, 0.72); --lg-blur: 24px; }
  .dark .lg-overlay.lg-regular { --lg-base: rgba(28, 30, 38, 0.74); }

  .lg-control { transition: scale 0.45s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.2s; }
  .lg-control:active:not(:disabled) { scale: 1.04 0.96; }

  /* Edge lensing: Chromium only (html.lg-lens is set by <LiquidFilters />). */
  html.lg-lens .lg:not(.lg-inline):not(.lg-overlay) {
    backdrop-filter: url(#lg-lens) blur(var(--lg-blur)) saturate(var(--lg-sat));
  }

  @media (prefers-reduced-motion: reduce) {
    .lg-control { transition: none; }
    .lg-control:active:not(:disabled) { scale: none; }
  }
  @media (prefers-reduced-transparency: reduce) {
    .lg.lg { --lg-base: color-mix(in oklab, var(--background) 94%, transparent); backdrop-filter: none; -webkit-backdrop-filter: none; }
  }
}

@keyframes shimmer {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(100%);
  }
}

/* Sonner toasts. Sonner injects unlayered CSS that beats the .lg layer, so the liquid material
   is re-applied here from the .lg variables (the toast and its buttons carry lg classes). */
[data-sonner-toast],
[data-sonner-toast] [data-button],
[data-sonner-toast] [data-cancel],
[data-sonner-toast] [data-close-button] {
  background:
    linear-gradient(var(--lg-wash), var(--lg-wash)) padding-box,
    linear-gradient(135deg, var(--lg-rim-hi), var(--lg-rim-lo) 35%, var(--lg-rim-lo) 65%, var(--lg-rim-hi)) border-box !important;
  border: 1px solid transparent !important;
  backdrop-filter: blur(var(--lg-blur)) saturate(var(--lg-sat)) !important;
  -webkit-backdrop-filter: blur(var(--lg-blur)) saturate(var(--lg-sat)) !important;
  color: var(--foreground) !important;
}

[data-sonner-toast] {
  border-radius: 20px !important;
  box-shadow: inset 0 1px 0 var(--lg-highlight), 0 1px 2px var(--lg-drop), 0 12px 32px -14px var(--lg-drop) !important;
}

[data-sonner-toast][data-type="success"] {
  border-color: rgba(34, 197, 94, 0.3) !important;
}

[data-sonner-toast][data-type="error"] {
  border-color: rgba(239, 68, 68, 0.3) !important;
}

[data-sonner-toast][data-type="warning"] {
  border-color: rgba(234, 179, 8, 0.3) !important;
}

[data-sonner-toast][data-type="info"] {
  border-color: rgba(59, 130, 246, 0.3) !important;
}

/* Toast description text */
[data-sonner-toast] [data-description] {
  color: var(--muted-foreground) !important;
}

[data-sonner-toast] [data-button],
[data-sonner-toast] [data-cancel] {
  border-radius: 9999px !important;
  transition: opacity 0.2s ease !important;
}

[data-sonner-toast] [data-cancel] {
  color: var(--muted-foreground) !important;
}

[data-sonner-toast] [data-button]:hover,
[data-sonner-toast] [data-cancel]:hover,
[data-sonner-toast] [data-close-button]:hover {
  opacity: 0.9 !important;
}
components/ui/badge.tsx
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"
import { liquidProps, resolveVariant, type LiquidProps } from "@/lib/liquid-glass/material"

const badgeVariants = cva(
  "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
  {
    variants: {
      variant: {
        liquid: "text-foreground",
        default:
          "border border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
        secondary:
          "border border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
        destructive:
          "border border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
        outline: "border text-foreground",
      },
    },
    defaultVariants: {
      variant: "liquid",
    },
  }
)

type BadgeVariant = NonNullable<VariantProps<typeof badgeVariants>["variant"]>

export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, LiquidProps {
  variant?: BadgeVariant | null
}

function Badge({ className, variant, material, tint, glass, webgl, style, ...props }: BadgeProps) {
  const v = resolveVariant<BadgeVariant>(variant ?? undefined, material)
  const lg = v.variant === "liquid" ? liquidProps({ role: "inline", material: v.material, tint, glass, webgl }) : null
  return (
    <div
      {...lg}
      className={cn(badgeVariants({ variant: v.variant }), lg?.className, className)}
      style={{ ...lg?.style, ...style }}
      {...props}
    />
  )
}

export { Badge, badgeVariants }

components/ui/glass/badge.tsx
"use client"

import * as React from "react"
import { Badge as BaseBadge } from "@/components/ui/badge"
import { cn } from "@/lib/utils"
import { hoverEffects, type HoverEffect } from "@/lib/hover-effects"

export interface BadgeProps extends React.ComponentProps<typeof BaseBadge> {
  glow?: boolean
  hover?: HoverEffect
}

/**
 * Glass UI Badge - Enhanced badge with glassy effects and glow option
 */
export function Badge({ className, glow = false, hover = "none", ...props }: BadgeProps) {
  return (
    <BaseBadge
      className={cn(
        "relative overflow-hidden",
        glow && "shadow-[0_0_0_1px_rgba(255,255,255,0.35),0_0_28px_-4px_rgba(255,255,255,0.45)]",
        "transition-all duration-200",
        hoverEffects({ hover }),
        className
      )}
      {...props}
    />
  )
}

More from glass-ui

Browse all components in this library →