Back to buttons
import { Slot, Slottable } from "@radix-ui/react-slot";
import type { ComponentProps, CSSProperties } from "react";

import { cn } from "@/registry/default/lib/utils";
import { buttonVariants } from "@/registry/default/ui/button";

interface DashButtonProps extends ComponentProps<"button"> {
  size?: "sm" | "default" | "lg";
  /** Render the single child element (a link, for example) with these styles instead. */
  asChild?: boolean;
  /** Colour of the dashed outline. Defaults to the muted foreground. */
  dashColor?: string;
}

/**
 * A pill button that trades its fill for a dashed outline on hover and
 * keyboard focus, with the dashes marching around it. The outline is one
 * SVG rectangle whose corners SVG rounds to the pill for any width, so
 * nothing is measured. The dashes hold still with `prefers-reduced-motion`
 * and stay paused while the button is at rest.
 *
 * @example
 * <DashButton>Plot a course</DashButton>
 */
function DashButton({
  size = "default",
  asChild = false,
  dashColor,
  className,
  children,
  style,
  ...props
}: DashButtonProps) {
  const Comp = asChild ? Slot : "button";

  return (
    <Comp
      data-slot="dash-button"
      className={cn(
        buttonVariants({ variant: "secondary", size }),
        "group/dash relative isolate rounded-full hover:bg-transparent focus-visible:bg-transparent",
        className
      )}
      style={{ ...(dashColor ? ({ "--dash-color": dashColor } as CSSProperties) : null), ...style }}
      {...props}
    >
      <svg
        aria-hidden="true"
        data-slot="dash-button-outline"
        className="pointer-events-none absolute inset-0 -z-10 size-full overflow-visible text-[color:var(--dash-color,var(--muted-foreground))] opacity-0 transition-opacity duration-200 group-hover/dash:opacity-100 group-focus-visible/dash:opacity-100 motion-reduce:transition-none"
      >
        {/* SVG clamps rx to half the height, so a large one always draws a pill. */}
        <rect
          width="100%"
          height="100%"
          rx="9999"
          fill="none"
          stroke="currentColor"
          strokeWidth="1"
          strokeDasharray="5 3"
          className="motion-safe:animate-dash-march [animation-play-state:paused] group-hover/dash:[animation-play-state:running] group-focus-visible/dash:[animation-play-state:running]"
        />
      </svg>
      <Slottable>{children}</Slottable>
    </Comp>
  );
}

export { DashButton, type DashButtonProps };

Dash Button

By kevingirelli

Try the interaction, copy the React source files, or download the complete example.

About Dash Button

Dash Button is an animated React and Tailwind CSS button by kevingirelli. Try the interaction, copy the React source, or download the example with creator credit.

Effects and categories
Hover effects, Animated
Using this example
Copy a TSX file or download the original source and standalone demo. Test keyboard focus, motion and contrast in your own project.

Usage, Tailwind and licensing questions

Read the source files

import-examples/dash-button.tsx

import {DashButton} from "@/registry/default/ui/dash-button";export default function Demo(){return <DashButton>Plot a course</DashButton>}

registry/default/ui/dash-button.tsx

import { Slot, Slottable } from "@radix-ui/react-slot";
import type { ComponentProps, CSSProperties } from "react";

import { cn } from "@/registry/default/lib/utils";
import { buttonVariants } from "@/registry/default/ui/button";

interface DashButtonProps extends ComponentProps<"button"> {
  size?: "sm" | "default" | "lg";
  /** Render the single child element (a link, for example) with these styles instead. */
  asChild?: boolean;
  /** Colour of the dashed outline. Defaults to the muted foreground. */
  dashColor?: string;
}

/**
 * A pill button that trades its fill for a dashed outline on hover and
 * keyboard focus, with the dashes marching around it. The outline is one
 * SVG rectangle whose corners SVG rounds to the pill for any width, so
 * nothing is measured. The dashes hold still with `prefers-reduced-motion`
 * and stay paused while the button is at rest.
 *
 * @example
 * <DashButton>Plot a course</DashButton>
 */
function DashButton({
  size = "default",
  asChild = false,
  dashColor,
  className,
  children,
  style,
  ...props
}: DashButtonProps) {
  const Comp = asChild ? Slot : "button";

  return (
    <Comp
      data-slot="dash-button"
      className={cn(
        buttonVariants({ variant: "secondary", size }),
        "group/dash relative isolate rounded-full hover:bg-transparent focus-visible:bg-transparent",
        className
      )}
      style={{ ...(dashColor ? ({ "--dash-color": dashColor } as CSSProperties) : null), ...style }}
      {...props}
    >
      <svg
        aria-hidden="true"
        data-slot="dash-button-outline"
        className="pointer-events-none absolute inset-0 -z-10 size-full overflow-visible text-[color:var(--dash-color,var(--muted-foreground))] opacity-0 transition-opacity duration-200 group-hover/dash:opacity-100 group-focus-visible/dash:opacity-100 motion-reduce:transition-none"
      >
        {/* SVG clamps rx to half the height, so a large one always draws a pill. */}
        <rect
          width="100%"
          height="100%"
          rx="9999"
          fill="none"
          stroke="currentColor"
          strokeWidth="1"
          strokeDasharray="5 3"
          className="motion-safe:animate-dash-march [animation-play-state:paused] group-hover/dash:[animation-play-state:running] group-focus-visible/dash:[animation-play-state:running]"
        />
      </svg>
      <Slottable>{children}</Slottable>
    </Comp>
  );
}

export { DashButton, type DashButtonProps };

registry/default/lib/utils.ts

import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

/**
 * Joins class names and lets the last conflicting Tailwind utility win, so a
 * `className` passed by the consumer always overrides the component default.
 */
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

registry/default/ui/button.tsx

import { Slot, Slottable } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import {LoaderCircleIcon} from "lucide-react";
import type { ComponentProps } from "react";

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

const buttonVariants = cva(
  [
    "inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap select-none",
    "transition-[color,background-color,border-color,box-shadow,opacity] outline-none motion-reduce:transition-none",
    "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
    "disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50",
    "aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-destructive/20",
    "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
  ],
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        outline: "border-input bg-field hover:bg-accent hover:text-accent-foreground border",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        destructive:
          "bg-destructive text-destructive-foreground hover:bg-destructive/90 focus-visible:ring-destructive/20",
        link: "text-primary underline-offset-4 hover:underline",
        // A face on a 4 px base that it sinks into while pressed. Both are
        // pseudo-elements in the button's own stacking context, below the
        // label: only the button's transform moves, and the base is moved
        // back so it stays put. mb-[4px] keeps it clear of what sits below.
        raised: [
          "text-primary-foreground relative isolate mb-[4px] bg-transparent hover:brightness-105",
          "transition-[color,background-color,border-color,box-shadow,opacity,transform,translate] duration-100",
          // The face (::after) sits on the base (::before), both under the label.
          "after:bg-primary after:absolute after:inset-0 after:-z-10 after:rounded-[inherit]",
          "before:bg-primary before:absolute before:inset-0 before:-z-20 before:translate-y-[4px] before:rounded-[inherit] before:brightness-75",
          "active:translate-y-[4px] active:before:translate-y-0",
        ],
      },
      size: {
        sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5",
        default: "h-9 px-4 py-2 has-[>svg]:px-3",
        lg: "h-10 px-6 has-[>svg]:px-4",
        "icon-sm": "size-8",
        icon: "size-9",
        "icon-lg": "size-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

interface ButtonProps extends ComponentProps<"button">, VariantProps<typeof buttonVariants> {
  /** Render the single child element (a link, for example) with the button styles instead. */
  asChild?: boolean;
  /** Shows a spinner and blocks interaction while an action is running. */
  loading?: boolean;
}

/**
 * `variant="raised"` stands on a 4 px base, darker than the face, and sinks
 * into it while pressed, like a key. It takes 4 px of margin below for the
 * base. With `prefers-reduced-motion` the press still moves, without a
 * transition.
 *
 * The icon sizes render a square button: give it an `aria-label`, since there
 * is no visible text to name it.
 *
 * Icons inside without a `size-*` class are set to `size-4`; give an icon its
 * own `size-*` (`size-5`, `size-[18px]`) to change it. `w-*` / `h-*` alone are
 * overridden.
 */
function Button({
  className,
  variant,
  size,
  asChild = false,
  loading = false,
  disabled,
  children,
  ...props
}: ButtonProps) {
  const Comp = asChild ? Slot : "button";
  const inactive = disabled || loading;

  return (
    <Comp
      data-slot="button"
      data-variant={variant ?? "default"}
      data-size={size ?? "default"}
      className={cn(buttonVariants({ variant, size, className }))}
      // A native button can be disabled; any other element only announces it.
      disabled={asChild ? undefined : inactive}
      aria-disabled={asChild && inactive ? true : undefined}
      aria-busy={loading || undefined}
      {...props}
    >
      {loading ? (
        <LoaderCircleIcon
          aria-hidden="true"
          data-slot="button-spinner"
          className="animate-spin motion-reduce:animate-[spin_1.5s_linear_infinite]"
        />
      ) : null}
      <Slottable>{children}</Slottable>
    </Comp>
  );
}

export { Button, buttonVariants, type ButtonProps };

demo.tsx

export { default as default } from "@/import-examples/dash-button";

CSS

/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
@layer properties;
@layer theme, base, components, utilities;
@layer theme {
  :root, :host {
    --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
      "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
      "Segoe UI Symbol", "Noto Color Emoji";
    --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
      "Courier New", monospace;
    --spacing: 0.25rem;
    --text-sm: 0.875rem;
    --text-sm--line-height: calc(1.25 / 0.875);
    --font-weight-medium: 500;
    --animate-spin: spin 1s linear infinite;
    --default-transition-duration: 150ms;
    --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
    --default-font-family: var(--font-sans);
    --default-mono-font-family: var(--font-mono);
  }
}
@layer base {
  *, ::after, ::before, ::backdrop, ::file-selector-button {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    border: 0 solid;
  }
  html, :host {
    line-height: 1.5;
    -webkit-text-size-adjust: 100%;
    tab-size: 4;
    font-family: var(--default-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
    font-feature-settings: var(--default-font-feature-settings, normal);
    font-variation-settings: var(--default-font-variation-settings, normal);
    -webkit-tap-highlight-color: transparent;
  }
  hr {
    height: 0;
    color: inherit;
    border-top-width: 1px;
  }
  abbr:where([title]) {
    -webkit-text-decoration: underline dotted;
    text-decoration: underline dotted;
  }
  h1, h2, h3, h4, h5, h6 {
    font-size: inherit;
    font-weight: inherit;
  }
  a {
    color: inherit;
    -webkit-text-decoration: inherit;
    text-decoration: inherit;
  }
  b, strong {
    font-weight: bolder;
  }
  code, kbd, samp, pre {
    font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
    font-feature-settings: var(--default-mono-font-feature-settings, normal);
    font-variation-settings: var(--default-mono-font-variation-settings, normal);
    font-size: 1em;
  }
  small {
    font-size: 80%;
  }
  sub, sup {
    font-size: 75%;
    line-height: 0;
    position: relative;
    vertical-align: baseline;
  }
  sub {
    bottom: -0.25em;
  }
  sup {
    top: -0.5em;
  }
  table {
    text-indent: 0;
    border-color: inherit;
    border-collapse: collapse;
  }
  :-moz-focusring:where(:not(iframe)) {
    outline: auto;
  }
  progress {
    vertical-align: baseline;
  }
  summary {
    display: list-item;
  }
  ol, ul, menu {
    list-style: none;
  }
  img, svg, video, canvas, audio, iframe, embed, object {
    display: block;
    vertical-align: middle;
  }
  img, video {
    max-width: 100%;
    height: auto;
  }
  button, input, select, optgroup, textarea, ::file-selector-button {
    font: inherit;
    font-feature-settings: inherit;
    font-variation-settings: inherit;
    letter-spacing: inherit;
    color: inherit;
    border-radius: 0;
    background-color: transparent;
    opacity: 1;
  }
  :where(select:is([multiple], [size])) optgroup {
    font-weight: bolder;
  }
  :where(select:is([multiple], [size])) optgroup option {
    padding-inline-start: 20px;
  }
  ::file-selector-button {
    margin-inline-end: 4px;
  }
  ::placeholder {
    opacity: 1;
  }
  @supports (not (-webkit-appearance: -apple-pay-button))  or (contain-intrinsic-size: 1px) {
    ::placeholder {
      color: currentcolor;
      @supports (color: color-mix(in lab, red, red)) {
        color: color-mix(in oklab, currentcolor 50%, transparent);
      }
    }
  }
  textarea {
    resize: vertical;
  }
  ::-webkit-search-decoration {
    -webkit-appearance: none;
  }
  ::-webkit-date-and-time-value {
    min-height: 1lh;
    text-align: inherit;
  }
  ::-webkit-datetime-edit {
    display: inline-flex;
  }
  ::-webkit-datetime-edit-fields-wrapper {
    padding: 0;
  }
  ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {
    padding-block: 0;
  }
  ::-webkit-calendar-picker-indicator {
    line-height: 1;
  }
  :-moz-ui-invalid {
    box-shadow: none;
  }
  button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button {
    appearance: button;
  }
  ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
    height: auto;
  }
  [hidden]:where(:not([hidden="until-found"])) {
    display: none !important;
  }
}
@layer utilities {
  .pointer-events-none {
    pointer-events: none;
  }
  .visible {
    visibility: visible;
  }
  .absolute {
    position: absolute;
  }
  .relative {
    position: relative;
  }
  .inset-0 {
    inset: 0px;
  }
  .isolate {
    isolation: isolate;
  }
  .-z-10 {
    z-index: calc(10 * -1);
  }
  .mb-\[4px\] {
    margin-bottom: 4px;
  }
  .inline-flex {
    display: inline-flex;
  }
  .size-4 {
    width: calc(var(--spacing) * 4);
    height: calc(var(--spacing) * 4);
  }
  .size-5 {
    width: calc(var(--spacing) * 5);
    height: calc(var(--spacing) * 5);
  }
  .size-8 {
    width: calc(var(--spacing) * 8);
    height: calc(var(--spacing) * 8);
  }
  .size-9 {
    width: calc(var(--spacing) * 9);
    height: calc(var(--spacing) * 9);
  }
  .size-10 {
    width: calc(var(--spacing) * 10);
    height: calc(var(--spacing) * 10);
  }
  .size-\[18px\] {
    width: 18px;
    height: 18px;
  }
  .size-full {
    width: 100%;
    height: 100%;
  }
  .h-8 {
    height: calc(var(--spacing) * 8);
  }
  .h-9 {
    height: calc(var(--spacing) * 9);
  }
  .h-10 {
    height: calc(var(--spacing) * 10);
  }
  .shrink-0 {
    flex-shrink: 0;
  }
  .transform {
    transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
  }
  .animate-spin {
    animation: var(--animate-spin);
  }
  .cursor-pointer {
    cursor: pointer;
  }
  .items-center {
    align-items: center;
  }
  .justify-center {
    justify-content: center;
  }
  .gap-1\.5 {
    gap: calc(var(--spacing) * 1.5);
  }
  .gap-2 {
    gap: calc(var(--spacing) * 2);
  }
  .overflow-visible {
    overflow: visible;
  }
  .rounded-full {
    border-radius: calc(infinity * 1px);
  }
  .rounded-md {
    border-radius: calc(var(--radius) - 2px);
  }
  .border {
    border-style: var(--tw-border-style);
    border-width: 1px;
  }
  .border-input {
    border-color: var(--input);
  }
  .bg-destructive {
    background-color: var(--destructive);
  }
  .bg-primary {
    background-color: var(--primary);
  }
  .bg-secondary {
    background-color: var(--secondary);
  }
  .bg-transparent {
    background-color: transparent;
  }
  .px-3 {
    padding-inline: calc(var(--spacing) * 3);
  }
  .px-4 {
    padding-inline: calc(var(--spacing) * 4);
  }
  .px-6 {
    padding-inline: calc(var(--spacing) * 6);
  }
  .py-2 {
    padding-block: calc(var(--spacing) * 2);
  }
  .text-sm {
    font-size: var(--text-sm);
    line-height: var(--tw-leading, var(--text-sm--line-height));
  }
  .font-medium {
    --tw-font-weight: var(--font-weight-medium);
    font-weight: var(--font-weight-medium);
  }
  .whitespace-nowrap {
    white-space: nowrap;
  }
  .text-\[color\:var\(--dash-color\,var\(--muted-foreground\)\)\] {
    color: var(--dash-color,var(--muted-foreground));
  }
  .text-destructive-foreground {
    color: var(--destructive-foreground);
  }
  .text-primary {
    color: var(--primary);
  }
  .text-primary-foreground {
    color: var(--primary-foreground);
  }
  .text-secondary-foreground {
    color: var(--secondary-foreground);
  }
  .underline-offset-4 {
    text-underline-offset: 4px;
  }
  .opacity-0 {
    opacity: 0%;
  }
  .outline {
    outline-style: var(--tw-outline-style);
    outline-width: 1px;
  }
  .transition-\[color\,background-color\,border-color\,box-shadow\,opacity\,transform\,translate\] {
    transition-property: color,background-color,border-color,box-shadow,opacity,transform,translate;
    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
    transition-duration: var(--tw-duration, var(--default-transition-duration));
  }
  .transition-\[color\,background-color\,border-color\,box-shadow\,opacity\] {
    transition-property: color,background-color,border-color,box-shadow,opacity;
    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
    transition-duration: var(--tw-duration, var(--default-transition-duration));
  }
  .transition-opacity {
    transition-property: opacity;
    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
    transition-duration: var(--tw-duration, var(--default-transition-duration));
  }
  .duration-100 {
    --tw-duration: 100ms;
    transition-duration: 100ms;
  }
  .duration-200 {
    --tw-duration: 200ms;
    transition-duration: 200ms;
  }
  .outline-none {
    --tw-outline-style: none;
    outline-style: none;
  }
  .select-none {
    -webkit-user-select: none;
    user-select: none;
  }
  .\[animation-play-state\:paused\] {
    animation-play-state: paused;
  }
  @media (hover: hover) {
    .group-hover\/dash\:opacity-100:is(:where(.group\/dash):hover *) {
      opacity: 100%;
    }
    .group-hover\/dash\:\[animation-play-state\:running\]:is(:where(.group\/dash):hover *) {
      animation-play-state: running;
    }
  }
  .group-focus-visible\/dash\:opacity-100:is(:where(.group\/dash):focus-visible *) {
    opacity: 100%;
  }
  .group-focus-visible\/dash\:\[animation-play-state\:running\]:is(:where(.group\/dash):focus-visible *) {
    animation-play-state: running;
  }
  .before\:absolute::before {
    content: var(--tw-content);
    position: absolute;
  }
  .before\:inset-0::before {
    content: var(--tw-content);
    inset: 0px;
  }
  .before\:-z-20::before {
    content: var(--tw-content);
    z-index: calc(20 * -1);
  }
  .before\:translate-y-\[4px\]::before {
    content: var(--tw-content);
    --tw-translate-y: 4px;
    translate: var(--tw-translate-x) var(--tw-translate-y);
  }
  .before\:rounded-\[inherit\]::before {
    content: var(--tw-content);
    border-radius: inherit;
  }
  .before\:bg-primary::before {
    content: var(--tw-content);
    background-color: var(--primary);
  }
  .before\:brightness-75::before {
    content: var(--tw-content);
    --tw-brightness: brightness(75%);
    filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
  }
  .after\:absolute::after {
    content: var(--tw-content);
    position: absolute;
  }
  .after\:inset-0::after {
    content: var(--tw-content);
    inset: 0px;
  }
  .after\:-z-10::after {
    content: var(--tw-content);
    z-index: calc(10 * -1);
  }
  .after\:rounded-\[inherit\]::after {
    content: var(--tw-content);
    border-radius: inherit;
  }
  .after\:bg-primary::after {
    content: var(--tw-content);
    background-color: var(--primary);
  }
  @media (hover: hover) {
    .hover\:bg-accent:hover {
      background-color: var(--accent);
    }
    .hover\:bg-destructive\/90:hover {
      background-color: var(--destructive);
    }
    @supports (color: color-mix(in lab, red, red)) {
      .hover\:bg-destructive\/90:hover {
        background-color: color-mix(in oklab, var(--destructive) 90%, transparent);
      }
    }
    .hover\:bg-primary\/90:hover {
      background-color: var(--primary);
    }
    @supports (color: color-mix(in lab, red, red)) {
      .hover\:bg-primary\/90:hover {
        background-color: color-mix(in oklab, var(--primary) 90%, transparent);
      }
    }
    .hover\:bg-secondary\/80:hover {
      background-color: var(--secondary);
    }
    @supports (color: color-mix(in lab, red, red)) {
      .hover\:bg-secondary\/80:hover {
        background-color: color-mix(in oklab, var(--secondary) 80%, transparent);
      }
    }
    .hover\:bg-transparent:hover {
      background-color: transparent;
    }
    .hover\:text-accent-foreground:hover {
      color: var(--accent-foreground);
    }
    .hover\:underline:hover {
      text-decoration-line: underline;
    }
    .hover\:brightness-105:hover {
      --tw-brightness: brightness(105%);
      filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
    }
  }
  .focus-visible\:border-ring:focus-visible {
    border-color: var(--ring);
  }
  .focus-visible\:bg-transparent:focus-visible {
    background-color: transparent;
  }
  .focus-visible\:ring-\[3px\]:focus-visible {
    --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
  }
  .focus-visible\:ring-destructive\/20:focus-visible {
    --tw-ring-color: var(--destructive);
    @supports (color: color-mix(in lab, red, red)) {
      --tw-ring-color: color-mix(in oklab, var(--destructive) 20%, transparent);
    }
  }
  .focus-visible\:ring-ring\/50:focus-visible {
    --tw-ring-color: var(--ring);
    @supports (color: color-mix(in lab, red, red)) {
      --tw-ring-color: color-mix(in oklab, var(--ring) 50%, transparent);
    }
  }
  .active\:translate-y-\[4px\]:active {
    --tw-translate-y: 4px;
    translate: var(--tw-translate-x) var(--tw-translate-y);
  }
  .active\:before\:translate-y-0:active::before {
    content: var(--tw-content);
    --tw-translate-y: 0px;
    translate: var(--tw-translate-x) var(--tw-translate-y);
  }
  .disabled\:pointer-events-none:disabled {
    pointer-events: none;
  }
  .disabled\:opacity-50:disabled {
    opacity: 50%;
  }
  .has-\[\>svg\]\:px-2\.5:has( > svg) {
    padding-inline: calc(var(--spacing) * 2.5);
  }
  .has-\[\>svg\]\:px-3:has( > svg) {
    padding-inline: calc(var(--spacing) * 3);
  }
  .has-\[\>svg\]\:px-4:has( > svg) {
    padding-inline: calc(var(--spacing) * 4);
  }
  .aria-disabled\:pointer-events-none[aria-disabled="true"] {
    pointer-events: none;
  }
  .aria-disabled\:opacity-50[aria-disabled="true"] {
    opacity: 50%;
  }
  .aria-\[invalid\=true\]\:border-destructive[aria-invalid="true"] {
    border-color: var(--destructive);
  }
  .aria-\[invalid\=true\]\:ring-destructive\/20[aria-invalid="true"] {
    --tw-ring-color: var(--destructive);
    @supports (color: color-mix(in lab, red, red)) {
      --tw-ring-color: color-mix(in oklab, var(--destructive) 20%, transparent);
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .motion-reduce\:animate-\[spin_1\.5s_linear_infinite\] {
      animation: spin 1.5s linear infinite;
    }
    .motion-reduce\:transition-none {
      transition-property: none;
    }
  }
  .\[\&_svg\]\:pointer-events-none svg {
    pointer-events: none;
  }
  .\[\&_svg\]\:shrink-0 svg {
    flex-shrink: 0;
  }
  .\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*='size-']) {
    width: calc(var(--spacing) * 4);
    height: calc(var(--spacing) * 4);
  }
}
:root {
  --background: #fff;
  --foreground: #111;
  --primary: #171717;
  --primary-foreground: #fff;
  --secondary: #eee;
  --secondary-foreground: #171717;
  --muted: #eee;
  --muted-foreground: #666;
  --accent: #eee;
  --accent-foreground: #171717;
  --border: #ddd;
  --input: #ddd;
  --ring: #777;
  --destructive: #dc2626;
  --destructive-foreground: #fff;
  --popover: #fff;
  --popover-foreground: #111;
  --success: #15803d;
  --success-foreground: #fff;
  --radius: .625rem;
  --hairline: 1px;
}
.dark {
  --background: #090909;
  --foreground: #fafafa;
  --primary: #fafafa;
  --primary-foreground: #171717;
  --secondary: #262626;
  --secondary-foreground: #fafafa;
  --muted: #262626;
  --muted-foreground: #a3a3a3;
  --accent: #262626;
  --accent-foreground: #fafafa;
  --border: #333;
  --input: #333;
  --popover: #171717;
  --popover-foreground: #fafafa;
}
@property --tw-rotate-x {
  syntax: "*";
  inherits: false;
}
@property --tw-rotate-y {
  syntax: "*";
  inherits: false;
}
@property --tw-rotate-z {
  syntax: "*";
  inherits: false;
}
@property --tw-skew-x {
  syntax: "*";
  inherits: false;
}
@property --tw-skew-y {
  syntax: "*";
  inherits: false;
}
@property --tw-border-style {
  syntax: "*";
  inherits: false;
  initial-value: solid;
}
@property --tw-font-weight {
  syntax: "*";
  inherits: false;
}
@property --tw-outline-style {
  syntax: "*";
  inherits: false;
  initial-value: solid;
}
@property --tw-duration {
  syntax: "*";
  inherits: false;
}
@property --tw-content {
  syntax: "*";
  initial-value: "";
  inherits: false;
}
@property --tw-translate-x {
  syntax: "*";
  inherits: false;
  initial-value: 0;
}
@property --tw-translate-y {
  syntax: "*";
  inherits: false;
  initial-value: 0;
}
@property --tw-translate-z {
  syntax: "*";
  inherits: false;
  initial-value: 0;
}
@property --tw-blur {
  syntax: "*";
  inherits: false;
}
@property --tw-brightness {
  syntax: "*";
  inherits: false;
}
@property --tw-contrast {
  syntax: "*";
  inherits: false;
}
@property --tw-grayscale {
  syntax: "*";
  inherits: false;
}
@property --tw-hue-rotate {
  syntax: "*";
  inherits: false;
}
@property --tw-invert {
  syntax: "*";
  inherits: false;
}
@property --tw-opacity {
  syntax: "*";
  inherits: false;
}
@property --tw-saturate {
  syntax: "*";
  inherits: false;
}
@property --tw-sepia {
  syntax: "*";
  inherits: false;
}
@property --tw-drop-shadow {
  syntax: "*";
  inherits: false;
}
@property --tw-drop-shadow-color {
  syntax: "*";
  inherits: false;
}
@property --tw-drop-shadow-alpha {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 100%;
}
@property --tw-drop-shadow-size {
  syntax: "*";
  inherits: false;
}
@property --tw-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}
@property --tw-shadow-color {
  syntax: "*";
  inherits: false;
}
@property --tw-shadow-alpha {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 100%;
}
@property --tw-inset-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}
@property --tw-inset-shadow-color {
  syntax: "*";
  inherits: false;
}
@property --tw-inset-shadow-alpha {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 100%;
}
@property --tw-ring-color {
  syntax: "*";
  inherits: false;
}
@property --tw-ring-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}
@property --tw-inset-ring-color {
  syntax: "*";
  inherits: false;
}
@property --tw-inset-ring-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}
@property --tw-ring-inset {
  syntax: "*";
  inherits: false;
}
@property --tw-ring-offset-width {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}
@property --tw-ring-offset-color {
  syntax: "*";
  inherits: false;
  initial-value: #fff;
}
@property --tw-ring-offset-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
@layer properties {
  @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
    *, ::before, ::after, ::backdrop {
      --tw-rotate-x: initial;
      --tw-rotate-y: initial;
      --tw-rotate-z: initial;
      --tw-skew-x: initial;
      --tw-skew-y: initial;
      --tw-border-style: solid;
      --tw-font-weight: initial;
      --tw-outline-style: solid;
      --tw-duration: initial;
      --tw-content: "";
      --tw-translate-x: 0;
      --tw-translate-y: 0;
      --tw-translate-z: 0;
      --tw-blur: initial;
      --tw-brightness: initial;
      --tw-contrast: initial;
      --tw-grayscale: initial;
      --tw-hue-rotate: initial;
      --tw-invert: initial;
      --tw-opacity: initial;
      --tw-saturate: initial;
      --tw-sepia: initial;
      --tw-drop-shadow: initial;
      --tw-drop-shadow-color: initial;
      --tw-drop-shadow-alpha: 100%;
      --tw-drop-shadow-size: initial;
      --tw-shadow: 0 0 #0000;
      --tw-shadow-color: initial;
      --tw-shadow-alpha: 100%;
      --tw-inset-shadow: 0 0 #0000;
      --tw-inset-shadow-color: initial;
      --tw-inset-shadow-alpha: 100%;
      --tw-ring-color: initial;
      --tw-ring-shadow: 0 0 #0000;
      --tw-inset-ring-color: initial;
      --tw-inset-ring-shadow: 0 0 #0000;
      --tw-ring-inset: initial;
      --tw-ring-offset-width: 0px;
      --tw-ring-offset-color: #fff;
      --tw-ring-offset-shadow: 0 0 #0000;
    }
  }
}