shadimage
Goose UI
uibaseline-statussource: ok · hit

Baseline Status

Display browser support status for web features based on the W3C WebDX Baseline specification. Includes real-time browser feature detection.

npx shadcn add "https://gooseui.pro/r/baseline-status.json"
free

Where it goes

statsfeature-grid

Interaction

animated-counter

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

Dependencies

Bundle weight · light
iconslucide-react

Files2 · source cached 10/14/2026, 3:01:20 PM

registry/new-york/ui/baseline-status.tsx
"use client"

import { CheckCircle2, XCircle } from "lucide-react"
import * as React from "react"
import {
  type BaselineFeature,
  type BaselineStatus as BaselineStatusType,
  getBaselineStatus,
  getBaselineStatusText,
} from "@/lib/baseline"
import { cn } from "@/lib/utils"

// ============================================================================
// Browser Feature Detection
// ============================================================================

export type BrowserFeature =
  | "view-transitions"
  | "popover"
  | "anchor-positioning"
  | "container-queries"
  | "has"
  | "scroll-driven-animations"
  | "text-wrap-balance"
  | "text-wrap-pretty"
  | "corner-shape"

function checkBrowserFeature(feature: BrowserFeature): boolean {
  if (typeof document === "undefined" || typeof window === "undefined")
    return false

  switch (feature) {
    case "view-transitions":
      return "startViewTransition" in document
    case "popover":
      return "popover" in HTMLElement.prototype
    case "anchor-positioning":
      return CSS.supports("anchor-name", "--test")
    case "container-queries":
      return CSS.supports("container-type", "inline-size")
    case "has":
      return CSS.supports("selector(:has(*))")
    case "scroll-driven-animations":
      return CSS.supports("animation-timeline", "scroll()")
    case "text-wrap-balance":
      return CSS.supports("text-wrap", "balance")
    case "text-wrap-pretty":
      return CSS.supports("text-wrap", "pretty")
    case "corner-shape":
      return CSS.supports("corner-shape", "squircle")
    default:
      return false
  }
}

function getBrowserFeatureLabel(feature: BrowserFeature): string {
  switch (feature) {
    case "view-transitions":
      return "View Transitions API"
    case "popover":
      return "Popover API"
    case "anchor-positioning":
      return "CSS Anchor Positioning"
    case "container-queries":
      return "Container Queries"
    case "has":
      return ":has() selector"
    case "scroll-driven-animations":
      return "Scroll-driven Animations"
    case "text-wrap-balance":
      return "text-wrap: balance"
    case "text-wrap-pretty":
      return "text-wrap: pretty"
    case "corner-shape":
      return "CSS corner-shape"
    default:
      return feature
  }
}

/**
 * Official Baseline icons from W3C WebDX Community Group
 * @see https://web-platform-dx.github.io/web-features/name-and-logo-usage-guidelines/
 * @license CC BY-ND 4.0
 */
const BaselineIcons = {
  widely: (
    <svg
      viewBox="0 0 540 300"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="h-5 w-auto"
      aria-hidden="true"
    >
      <path
        d="M420 30L390 60L480 150L390 240L330 180L300 210L390 300L540 150L420 30Z"
        fill="#C4EED0"
      />
      <path
        d="M150 0L30 120L60 150L150 60L210 120L240 90L150 0Z"
        fill="#C4EED0"
      />
      <path
        d="M390 0L420 30L150 300L0 150L30 120L150 240L390 0Z"
        fill="#1EA446"
      />
    </svg>
  ),
  newly: (
    <svg
      viewBox="0 0 540 300"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="h-5 w-auto"
      aria-hidden="true"
    >
      <path d="M150 0L180 30L150 60L120 30L150 0Z" fill="#A8C7FA" />
      <path d="M210 60L240 90L210 120L180 90L210 60Z" fill="#A8C7FA" />
      <path d="M450 60L480 90L450 120L420 90L450 60Z" fill="#A8C7FA" />
      <path d="M510 120L540 150L510 180L480 150L510 120Z" fill="#A8C7FA" />
      <path d="M450 180L480 210L450 240L420 210L450 180Z" fill="#A8C7FA" />
      <path d="M390 240L420 270L390 300L360 270L390 240Z" fill="#A8C7FA" />
      <path d="M330 180L360 210L330 240L300 210L330 180Z" fill="#A8C7FA" />
      <path d="M90 60L120 90L90 120L60 90L90 60Z" fill="#A8C7FA" />
      <path
        d="M390 0L420 30L150 300L0 150L30 120L150 240L390 0Z"
        fill="#1B6EF3"
      />
    </svg>
  ),
  limited: (
    <svg
      viewBox="0 0 540 300"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="h-5 w-auto"
      aria-hidden="true"
    >
      <path d="M150 0L240 90L210 120L120 30L150 0Z" fill="#F09409" />
      <path
        d="M420 30L540 150L420 270L390 240L480 150L390 60L420 30Z"
        fill="#C6C6C6"
      />
      <path d="M330 180L300 210L390 300L420 270L330 180Z" fill="#F09409" />
      <path
        d="M120 30L150 60L60 150L150 240L120 270L0 150L120 30Z"
        fill="#C6C6C6"
      />
      <path d="M390 0L420 30L150 300L120 270L390 0Z" fill="#F09409" />
    </svg>
  ),
  no_data: (
    <svg
      viewBox="0 0 540 300"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="h-5 w-auto opacity-40"
      aria-hidden="true"
    >
      <path
        d="M420 30L540 150L420 270L390 240L480 150L390 60L420 30Z"
        fill="currentColor"
      />
      <path
        d="M120 30L150 60L60 150L150 240L120 270L0 150L120 30Z"
        fill="currentColor"
      />
      <path d="M390 0L420 30L150 300L120 270L390 0Z" fill="currentColor" />
    </svg>
  ),
}

const sizeClasses = {
  sm: "text-xs gap-1.5",
  md: "text-sm gap-2",
  lg: "text-base gap-2.5",
}

const iconSizeClasses = {
  sm: "[&_svg]:h-4",
  md: "[&_svg]:h-5",
  lg: "[&_svg]:h-6",
}

export interface BaselineStatusProps
  extends React.HTMLAttributes<HTMLDivElement> {
  /** Feature ID from web-features (e.g., "scroll-snap", "container-queries") */
  featureId?: string
  /** Direct status override (use instead of featureId for static data) */
  status?: BaselineStatusType
  /** Year when the feature became available */
  year?: number
  /** Size variant */
  size?: "sm" | "md" | "lg"
  /** Show year in the label */
  showYear?: boolean
  /** Show only the icon without text */
  iconOnly?: boolean
  /** Browser feature to check support for */
  browserCheck?: BrowserFeature
}

/**
 * BaselineStatus displays browser support status for web features
 * based on the W3C WebDX Baseline specification.
 *
 * @see https://web.dev/baseline
 * @see https://github.com/web-platform-dx/web-features
 *
 * @example
 * // With featureId (fetches data at build time)
 * <BaselineStatus featureId="scroll-snap" />
 *
 * @example
 * // With static data (no API call)
 * <BaselineStatus status="widely" year={2022} />
 *
 * @example
 * // Icon only
 * <BaselineStatus featureId="container-queries" iconOnly />
 */
export function BaselineStatus({
  featureId,
  status: statusProp,
  year: yearProp,
  size = "md",
  showYear = true,
  iconOnly = false,
  browserCheck,
  className,
  ...props
}: BaselineStatusProps) {
  const [feature, setFeature] = React.useState<BaselineFeature | null>(
    statusProp ? { status: statusProp, year: yearProp } : null,
  )
  const [isLoading, setIsLoading] = React.useState(!statusProp && !!featureId)
  const [browserSupport, setBrowserSupport] = React.useState<boolean | null>(
    null,
  )

  React.useEffect(() => {
    if (statusProp || !featureId) return

    let cancelled = false
    setIsLoading(true)

    getBaselineStatus(featureId).then((data) => {
      if (!cancelled) {
        setFeature(data)
        setIsLoading(false)
      }
    })

    return () => {
      cancelled = true
    }
  }, [featureId, statusProp])

  // Check browser support on mount
  React.useEffect(() => {
    if (browserCheck) {
      setBrowserSupport(checkBrowserFeature(browserCheck))
    }
  }, [browserCheck])

  if (isLoading) {
    return (
      <div
        className={cn(
          "inline-flex items-center animate-pulse",
          sizeClasses[size],
          className,
        )}
        {...props}
      >
        <div className="h-5 w-9 rounded-full bg-muted" />
        {!iconOnly && <div className="h-4 w-24 rounded bg-muted" />}
      </div>
    )
  }

  const status = feature?.status ?? "no_data"
  const year = showYear ? feature?.year : undefined
  const label = getBaselineStatusText(status, year)
  const icon = BaselineIcons[status]

  // If browserCheck is provided, render combined layout
  if (browserCheck && browserSupport !== null) {
    const featureLabel = getBrowserFeatureLabel(browserCheck)

    return (
      <div
        className={cn(
          "inline-flex flex-wrap items-center gap-3 rounded-lg border border-border/50 bg-muted/30 px-3 py-2",
          className,
        )}
        role="status"
        {...props}
      >
        {/* Baseline Status */}
        <div
          className={cn(
            "flex items-center",
            sizeClasses[size],
            iconSizeClasses[size],
          )}
        >
          {icon}
          {!iconOnly && (
            <span className="text-muted-foreground whitespace-nowrap">
              {label}
            </span>
          )}
        </div>

        {/* Browser Support Check */}
        <div
          className={cn(
            "flex items-center gap-1.5 text-sm",
            browserSupport
              ? "text-green-600 dark:text-green-400"
              : "text-red-600 dark:text-red-400",
          )}
        >
          {browserSupport ? (
            <CheckCircle2 className="h-4 w-4 shrink-0" />
          ) : (
            <XCircle className="h-4 w-4 shrink-0" />
          )}
          <span>
            {browserSupport ? "Your browser supports" : "Not supported:"}{" "}
            {featureLabel}
          </span>
        </div>
      </div>
    )
  }

  return (
    <div
      className={cn(
        "inline-flex items-center",
        sizeClasses[size],
        iconSizeClasses[size],
        className,
      )}
      role="status"
      aria-label={label}
      {...props}
    >
      {icon}
      {!iconOnly && (
        <span className="text-muted-foreground whitespace-nowrap">{label}</span>
      )}
    </div>
  )
}

/**
 * Server Component wrapper for BaselineStatus
 * Use this in Server Components to fetch data at build time
 *
 * @example
 * // In a Server Component
 * const feature = await getBaselineStatus("scroll-snap")
 * <BaselineStatusStatic {...feature} />
 */
export function BaselineStatusStatic({
  status,
  year,
  size = "md",
  showYear = true,
  iconOnly = false,
  className,
  ...props
}: Omit<BaselineStatusProps, "featureId"> & {
  status: BaselineStatusType
}) {
  const label = getBaselineStatusText(status, showYear ? year : undefined)
  const icon = BaselineIcons[status]

  return (
    <div
      className={cn(
        "inline-flex items-center",
        sizeClasses[size],
        iconSizeClasses[size],
        className,
      )}
      role="status"
      aria-label={label}
      {...props}
    >
      {icon}
      {!iconOnly && (
        <span className="text-muted-foreground whitespace-nowrap">{label}</span>
      )}
    </div>
  )
}
lib/baseline.ts
/**
 * Baseline API utility for fetching web feature browser support status
 * @see https://web.dev/baseline
 * @see https://api.webstatus.dev/v1/features/
 */

const BASELINE_API = "https://api.webstatus.dev/v1/features"

export type BaselineStatus = "widely" | "newly" | "limited" | "no_data"

export interface BaselineFeature {
  status: BaselineStatus
  lowDate?: string // "2020-01-15" - when feature became newly available
  highDate?: string // "2022-07-15" - when feature became widely available
  year?: number // extracted year from highDate or lowDate
}

export interface BrowserImplementation {
  date: string
  status: "available" | "unavailable"
  version: string
}

export interface BaselineAPIResponse {
  baseline: {
    status: "widely" | "newly" | "limited"
    low_date?: string
    high_date?: string
  } | null
  browser_implementations?: Record<string, BrowserImplementation>
  feature_id: string
  name: string
}

/**
 * Fetches Baseline status for a web feature
 * Use this at build time or in server components for caching
 */
export async function getBaselineStatus(
  featureId: string,
): Promise<BaselineFeature> {
  try {
    const response = await fetch(`${BASELINE_API}/${featureId}`, {
      next: { revalidate: 86400 }, // Cache for 24 hours
    })

    if (!response.ok) {
      console.warn(`Baseline API: Feature "${featureId}" not found`)
      return { status: "no_data" }
    }

    const data: BaselineAPIResponse = await response.json()

    if (!data.baseline) {
      return { status: "no_data" }
    }

    const { status, low_date, high_date } = data.baseline

    // Extract year from the relevant date
    const relevantDate = status === "widely" ? high_date : low_date
    const year = relevantDate ? new Date(relevantDate).getFullYear() : undefined

    return {
      status,
      lowDate: low_date,
      highDate: high_date,
      year,
    }
  } catch (error) {
    console.error(`Baseline API error for "${featureId}":`, error)
    return { status: "no_data" }
  }
}

/**
 * Get display text for Baseline status
 */
export function getBaselineStatusText(
  status: BaselineStatus,
  year?: number,
): string {
  switch (status) {
    case "widely":
      return year ? `Widely available since ${year}` : "Widely available"
    case "newly":
      return year ? `Newly available since ${year}` : "Newly available"
    case "limited":
      return "Limited availability"
    default:
      return "No data"
  }
}

/**
 * Common feature IDs for reference
 * @see https://github.com/web-platform-dx/web-features/tree/main/features
 */
export const FEATURE_IDS = {
  // CSS
  scrollSnap: "scroll-snap",
  containerQueries: "container-queries",
  cssGrid: "css-grid",
  flexbox: "flexbox",
  cssVariables: "css-variables",
  anchorPositioning: "anchor-positioning",
  viewTransitions: "view-transitions",
  popover: "popover",

  // JavaScript
  promises: "promises",
  asyncAwait: "async-await",
  modules: "modules",

  // APIs
  intersectionObserver: "intersection-observer",
  resizeObserver: "resize-observer",
  webAnimations: "web-animations",
} as const

More from Goose UI

Browse all components in this library →