Back to buttons
"use client";

/**
 * @author: @dorianbaffier
 * @description: Command Button
 * @version: 2.0.0
 * @date: 2026-10-16
 * @license: MIT
 * @website: https://kokonutui.com
 * @github: https://github.com/kokonut-labs/kokonutui
 *
 * A button with its keyboard shortcut drawn as physical keycaps. The shortcut
 * works for real: pressing the keys anywhere on the page sinks each cap as
 * its key goes down and clicks the button when the combination matches.
 */

import {
  type ComponentProps,
  type ReactNode,
  useEffect,
  useRef,
  useState,
} from "react";
import { cn } from "@/lib/utils";

type Modifier = "mod" | "ctrl" | "alt" | "shift";

interface ParsedShortcut {
  modifiers: Modifier[];
  key: string;
}

interface CommandButtonProps extends ComponentProps<"button"> {
  children?: ReactNode;
  /**
   * The shortcut, as `+`-joined parts: `mod` (⌘ on Mac, Ctrl elsewhere),
   * `ctrl`, `alt`, `shift` and one key, e.g. `"mod+k"`, `"shift+n"`, `"c"`.
   */
  shortcut?: string;
  /**
   * Listen for the shortcut on the whole page. Shortcuts without `mod`,
   * `ctrl` or `alt` are ignored while typing in a field.
   */
  global?: boolean;
}

const APPLE_PLATFORM = /mac|iphone|ipad|ipod/i;
const MODIFIERS = new Set<Modifier>(["mod", "ctrl", "alt", "shift"]);

const parseShortcut = (shortcut: string): ParsedShortcut => {
  const parts = shortcut
    .toLowerCase()
    .split("+")
    .map((part) => part.trim())
    .filter(Boolean);
  const modifiers = parts.filter((part): part is Modifier =>
    MODIFIERS.has(part as Modifier)
  );
  const key = parts.find((part) => !MODIFIERS.has(part as Modifier)) ?? "k";
  return { modifiers, key };
};

const MAC_LABELS: Record<Modifier, string> = {
  mod: "⌘",
  ctrl: "⌃",
  alt: "⌥",
  shift: "⇧",
};
const PC_LABELS: Record<Modifier, string> = {
  mod: "Ctrl",
  ctrl: "Ctrl",
  alt: "Alt",
  shift: "Shift",
};
// aria-keyshortcuts takes KeyboardEvent.key names: Meta, not Command.
const ARIA_KEYS: Record<Modifier, [mac: string, pc: string]> = {
  mod: ["Meta", "Control"],
  ctrl: ["Control", "Control"],
  alt: ["Alt", "Alt"],
  shift: ["Shift", "Shift"],
};

const ariaKey = (key: string) => {
  if (key === "enter") {
    return "Enter";
  }
  if (key === "space") {
    return "Space";
  }
  return key.length === 1 ? key.toUpperCase() : key;
};

const keyLabel = (key: string) => {
  if (key === "enter") {
    return "↵";
  }
  if (key === "space") {
    return "Space";
  }
  return key.length === 1 ? key.toUpperCase() : key;
};

// Matches by physical key too, since Option on a Mac changes `event.key`
// (Option+K types "˚").
const isKey = (event: KeyboardEvent, key: string) => {
  const pressed = event.key.toLowerCase();
  if (key === "space") {
    return event.code === "Space";
  }
  return (
    pressed === key ||
    event.code === `Key${key.toUpperCase()}` ||
    event.code === `Digit${key}`
  );
};

const isModifierDown = (
  modifier: Modifier,
  event: KeyboardEvent,
  isMac: boolean
) => {
  switch (modifier) {
    case "mod":
      return isMac ? event.metaKey : event.ctrlKey;
    case "ctrl":
      return event.ctrlKey;
    case "alt":
      return event.altKey;
    case "shift":
      return event.shiftKey;
    default:
      return false;
  }
};

const matches = (
  event: KeyboardEvent,
  { modifiers, key }: ParsedShortcut,
  isMac: boolean
) => {
  if (!isKey(event, key)) {
    return false;
  }
  const wanted = {
    meta: modifiers.includes("mod") && isMac,
    ctrl: modifiers.includes("ctrl") || (modifiers.includes("mod") && !isMac),
    alt: modifiers.includes("alt"),
    shift: modifiers.includes("shift"),
  };
  return (
    event.metaKey === wanted.meta &&
    event.ctrlKey === wanted.ctrl &&
    event.altKey === wanted.alt &&
    event.shiftKey === wanted.shift
  );
};

const isTyping = (target: EventTarget | null) =>
  target instanceof HTMLElement &&
  (target.isContentEditable ||
    target.tagName === "INPUT" ||
    target.tagName === "TEXTAREA" ||
    target.tagName === "SELECT");

function Keycap({ label, pressed }: { label: string; pressed: boolean }) {
  return (
    <kbd
      className={cn(
        "inline-flex h-6 min-w-6 items-center justify-center rounded-md px-1.5 font-medium font-sans text-[11px] leading-none",
        "border border-zinc-200 bg-white text-zinc-600",
        "dark:border-white/10 dark:bg-zinc-800 dark:text-zinc-300",
        // The cap stands 2px above its base. Pressing sinks it onto the base:
        // a translate and a shorter shadow, never a scale.
        "shadow-[0_2px_0_0_var(--color-zinc-200)] dark:shadow-[0_2px_0_0_var(--color-zinc-950)]",
        "transition-[translate,box-shadow] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)]",
        "group-active/command:translate-y-0.5 group-active/command:shadow-none group-active/command:duration-75",
        pressed && "translate-y-0.5 shadow-none duration-75"
      )}
      data-pressed={pressed || undefined}
    >
      {label}
    </kbd>
  );
}

export default function CommandButton({
  className,
  children = "Search",
  shortcut = "mod+k",
  global = true,
  ...props
}: CommandButtonProps) {
  const buttonRef = useRef<HTMLButtonElement>(null);
  const [isMac, setIsMac] = useState(true);
  const [platformKnown, setPlatformKnown] = useState(false);
  const [held, setHeld] = useState<{ modifiers: Modifier[]; key: boolean }>({
    modifiers: [],
    key: false,
  });
  const parsed = parseShortcut(shortcut);
  const parsedRef = useRef(parsed);
  parsedRef.current = parsed;

  // The platform is unknown on the server, so the caps render invisible and
  // fade in once it is read, instead of swapping ⌘ for Ctrl in view.
  useEffect(() => {
    const platform =
      (navigator as Navigator & { userAgentData?: { platform?: string } })
        .userAgentData?.platform ?? navigator.platform;
    setIsMac(APPLE_PLATFORM.test(platform));
    setPlatformKnown(true);
  }, []);

  useEffect(() => {
    if (!global) {
      return;
    }
    const sync = (event: KeyboardEvent, isDown: boolean) => {
      const { modifiers, key } = parsedRef.current;
      const next = {
        modifiers: modifiers.filter((m) => isModifierDown(m, event, isMac)),
        key: isDown && isKey(event, key),
      };
      // Every key on the page lands here; only re-render when a cap moves.
      setHeld((current) =>
        current.key === next.key &&
        current.modifiers.join() === next.modifiers.join()
          ? current
          : next
      );
    };
    const handleKeyDown = (event: KeyboardEvent) => {
      const current = parsedRef.current;
      const needsModifier = current.modifiers.some((m) => m !== "shift");
      if (!needsModifier && isTyping(event.target)) {
        return;
      }
      sync(event, true);
      if (
        !event.repeat &&
        matches(event, current, isMac) &&
        !buttonRef.current?.disabled
      ) {
        event.preventDefault();
        buttonRef.current?.click();
      }
    };
    const handleKeyUp = (event: KeyboardEvent) => sync(event, false);
    const release = () =>
      setHeld((current) =>
        current.key || current.modifiers.length > 0
          ? { modifiers: [], key: false }
          : current
      );

    window.addEventListener("keydown", handleKeyDown);
    window.addEventListener("keyup", handleKeyUp);
    window.addEventListener("blur", release);
    return () => {
      window.removeEventListener("keydown", handleKeyDown);
      window.removeEventListener("keyup", handleKeyUp);
      window.removeEventListener("blur", release);
    };
  }, [global, isMac]);

  const labels = isMac ? MAC_LABELS : PC_LABELS;
  const keyshortcuts = [
    ...parsed.modifiers.map((m) => ARIA_KEYS[m][isMac ? 0 : 1]),
    ariaKey(parsed.key),
  ].join("+");

  return (
    <button
      aria-keyshortcuts={keyshortcuts}
      className={cn(
        "group/command inline-flex h-10 cursor-pointer items-center gap-3 rounded-xl py-1 pr-2 pl-3.5 font-medium text-sm",
        "border border-zinc-200 bg-white text-zinc-700",
        "dark:border-white/10 dark:bg-zinc-900 dark:text-zinc-200",
        "transition-[background-color,border-color] duration-150 ease-out",
        "hover:border-zinc-300 hover:bg-zinc-50 dark:hover:border-white/15 dark:hover:bg-zinc-800/70",
        "active:bg-zinc-100 dark:active:bg-zinc-800",
        "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-zinc-400/50 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-zinc-950",
        "disabled:pointer-events-none disabled:opacity-50",
        className
      )}
      ref={buttonRef}
      type="button"
      {...props}
    >
      {children}
      <span
        aria-hidden="true"
        className={cn(
          "flex items-center gap-1 pb-0.5 transition-opacity duration-150 ease-out",
          !platformKnown && "opacity-0"
        )}
      >
        {parsed.modifiers.map((modifier) => (
          <Keycap
            key={modifier}
            label={labels[modifier]}
            pressed={held.modifiers.includes(modifier)}
          />
        ))}
        <Keycap label={keyLabel(parsed.key)} pressed={held.key} />
      </span>
    </button>
  );
}

Command Button

By Kokonut UI

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

About Command Button

Command Button is an animated React and Tailwind CSS button by Kokonut UI. 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/command-button.tsx

import CommandButton from "@/components/kokonutui/command-button";
export default function ImportedDemo(){return <>
    <div className="flex flex-wrap items-center justify-center gap-3">
        <CommandButton global={false} />
        <CommandButton shortcut="c">New issue</CommandButton>
        <CommandButton shortcut="shift+n">New project</CommandButton>
    </div>
</>}

components/kokonutui/command-button.tsx

"use client";

/**
 * @author: @dorianbaffier
 * @description: Command Button
 * @version: 2.0.0
 * @date: 2026-10-16
 * @license: MIT
 * @website: https://kokonutui.com
 * @github: https://github.com/kokonut-labs/kokonutui
 *
 * A button with its keyboard shortcut drawn as physical keycaps. The shortcut
 * works for real: pressing the keys anywhere on the page sinks each cap as
 * its key goes down and clicks the button when the combination matches.
 */

import {
  type ComponentProps,
  type ReactNode,
  useEffect,
  useRef,
  useState,
} from "react";
import { cn } from "@/lib/utils";

type Modifier = "mod" | "ctrl" | "alt" | "shift";

interface ParsedShortcut {
  modifiers: Modifier[];
  key: string;
}

interface CommandButtonProps extends ComponentProps<"button"> {
  children?: ReactNode;
  /**
   * The shortcut, as `+`-joined parts: `mod` (⌘ on Mac, Ctrl elsewhere),
   * `ctrl`, `alt`, `shift` and one key, e.g. `"mod+k"`, `"shift+n"`, `"c"`.
   */
  shortcut?: string;
  /**
   * Listen for the shortcut on the whole page. Shortcuts without `mod`,
   * `ctrl` or `alt` are ignored while typing in a field.
   */
  global?: boolean;
}

const APPLE_PLATFORM = /mac|iphone|ipad|ipod/i;
const MODIFIERS = new Set<Modifier>(["mod", "ctrl", "alt", "shift"]);

const parseShortcut = (shortcut: string): ParsedShortcut => {
  const parts = shortcut
    .toLowerCase()
    .split("+")
    .map((part) => part.trim())
    .filter(Boolean);
  const modifiers = parts.filter((part): part is Modifier =>
    MODIFIERS.has(part as Modifier)
  );
  const key = parts.find((part) => !MODIFIERS.has(part as Modifier)) ?? "k";
  return { modifiers, key };
};

const MAC_LABELS: Record<Modifier, string> = {
  mod: "⌘",
  ctrl: "⌃",
  alt: "⌥",
  shift: "⇧",
};
const PC_LABELS: Record<Modifier, string> = {
  mod: "Ctrl",
  ctrl: "Ctrl",
  alt: "Alt",
  shift: "Shift",
};
// aria-keyshortcuts takes KeyboardEvent.key names: Meta, not Command.
const ARIA_KEYS: Record<Modifier, [mac: string, pc: string]> = {
  mod: ["Meta", "Control"],
  ctrl: ["Control", "Control"],
  alt: ["Alt", "Alt"],
  shift: ["Shift", "Shift"],
};

const ariaKey = (key: string) => {
  if (key === "enter") {
    return "Enter";
  }
  if (key === "space") {
    return "Space";
  }
  return key.length === 1 ? key.toUpperCase() : key;
};

const keyLabel = (key: string) => {
  if (key === "enter") {
    return "↵";
  }
  if (key === "space") {
    return "Space";
  }
  return key.length === 1 ? key.toUpperCase() : key;
};

// Matches by physical key too, since Option on a Mac changes `event.key`
// (Option+K types "˚").
const isKey = (event: KeyboardEvent, key: string) => {
  const pressed = event.key.toLowerCase();
  if (key === "space") {
    return event.code === "Space";
  }
  return (
    pressed === key ||
    event.code === `Key${key.toUpperCase()}` ||
    event.code === `Digit${key}`
  );
};

const isModifierDown = (
  modifier: Modifier,
  event: KeyboardEvent,
  isMac: boolean
) => {
  switch (modifier) {
    case "mod":
      return isMac ? event.metaKey : event.ctrlKey;
    case "ctrl":
      return event.ctrlKey;
    case "alt":
      return event.altKey;
    case "shift":
      return event.shiftKey;
    default:
      return false;
  }
};

const matches = (
  event: KeyboardEvent,
  { modifiers, key }: ParsedShortcut,
  isMac: boolean
) => {
  if (!isKey(event, key)) {
    return false;
  }
  const wanted = {
    meta: modifiers.includes("mod") && isMac,
    ctrl: modifiers.includes("ctrl") || (modifiers.includes("mod") && !isMac),
    alt: modifiers.includes("alt"),
    shift: modifiers.includes("shift"),
  };
  return (
    event.metaKey === wanted.meta &&
    event.ctrlKey === wanted.ctrl &&
    event.altKey === wanted.alt &&
    event.shiftKey === wanted.shift
  );
};

const isTyping = (target: EventTarget | null) =>
  target instanceof HTMLElement &&
  (target.isContentEditable ||
    target.tagName === "INPUT" ||
    target.tagName === "TEXTAREA" ||
    target.tagName === "SELECT");

function Keycap({ label, pressed }: { label: string; pressed: boolean }) {
  return (
    <kbd
      className={cn(
        "inline-flex h-6 min-w-6 items-center justify-center rounded-md px-1.5 font-medium font-sans text-[11px] leading-none",
        "border border-zinc-200 bg-white text-zinc-600",
        "dark:border-white/10 dark:bg-zinc-800 dark:text-zinc-300",
        // The cap stands 2px above its base. Pressing sinks it onto the base:
        // a translate and a shorter shadow, never a scale.
        "shadow-[0_2px_0_0_var(--color-zinc-200)] dark:shadow-[0_2px_0_0_var(--color-zinc-950)]",
        "transition-[translate,box-shadow] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)]",
        "group-active/command:translate-y-0.5 group-active/command:shadow-none group-active/command:duration-75",
        pressed && "translate-y-0.5 shadow-none duration-75"
      )}
      data-pressed={pressed || undefined}
    >
      {label}
    </kbd>
  );
}

export default function CommandButton({
  className,
  children = "Search",
  shortcut = "mod+k",
  global = true,
  ...props
}: CommandButtonProps) {
  const buttonRef = useRef<HTMLButtonElement>(null);
  const [isMac, setIsMac] = useState(true);
  const [platformKnown, setPlatformKnown] = useState(false);
  const [held, setHeld] = useState<{ modifiers: Modifier[]; key: boolean }>({
    modifiers: [],
    key: false,
  });
  const parsed = parseShortcut(shortcut);
  const parsedRef = useRef(parsed);
  parsedRef.current = parsed;

  // The platform is unknown on the server, so the caps render invisible and
  // fade in once it is read, instead of swapping ⌘ for Ctrl in view.
  useEffect(() => {
    const platform =
      (navigator as Navigator & { userAgentData?: { platform?: string } })
        .userAgentData?.platform ?? navigator.platform;
    setIsMac(APPLE_PLATFORM.test(platform));
    setPlatformKnown(true);
  }, []);

  useEffect(() => {
    if (!global) {
      return;
    }
    const sync = (event: KeyboardEvent, isDown: boolean) => {
      const { modifiers, key } = parsedRef.current;
      const next = {
        modifiers: modifiers.filter((m) => isModifierDown(m, event, isMac)),
        key: isDown && isKey(event, key),
      };
      // Every key on the page lands here; only re-render when a cap moves.
      setHeld((current) =>
        current.key === next.key &&
        current.modifiers.join() === next.modifiers.join()
          ? current
          : next
      );
    };
    const handleKeyDown = (event: KeyboardEvent) => {
      const current = parsedRef.current;
      const needsModifier = current.modifiers.some((m) => m !== "shift");
      if (!needsModifier && isTyping(event.target)) {
        return;
      }
      sync(event, true);
      if (
        !event.repeat &&
        matches(event, current, isMac) &&
        !buttonRef.current?.disabled
      ) {
        event.preventDefault();
        buttonRef.current?.click();
      }
    };
    const handleKeyUp = (event: KeyboardEvent) => sync(event, false);
    const release = () =>
      setHeld((current) =>
        current.key || current.modifiers.length > 0
          ? { modifiers: [], key: false }
          : current
      );

    window.addEventListener("keydown", handleKeyDown);
    window.addEventListener("keyup", handleKeyUp);
    window.addEventListener("blur", release);
    return () => {
      window.removeEventListener("keydown", handleKeyDown);
      window.removeEventListener("keyup", handleKeyUp);
      window.removeEventListener("blur", release);
    };
  }, [global, isMac]);

  const labels = isMac ? MAC_LABELS : PC_LABELS;
  const keyshortcuts = [
    ...parsed.modifiers.map((m) => ARIA_KEYS[m][isMac ? 0 : 1]),
    ariaKey(parsed.key),
  ].join("+");

  return (
    <button
      aria-keyshortcuts={keyshortcuts}
      className={cn(
        "group/command inline-flex h-10 cursor-pointer items-center gap-3 rounded-xl py-1 pr-2 pl-3.5 font-medium text-sm",
        "border border-zinc-200 bg-white text-zinc-700",
        "dark:border-white/10 dark:bg-zinc-900 dark:text-zinc-200",
        "transition-[background-color,border-color] duration-150 ease-out",
        "hover:border-zinc-300 hover:bg-zinc-50 dark:hover:border-white/15 dark:hover:bg-zinc-800/70",
        "active:bg-zinc-100 dark:active:bg-zinc-800",
        "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-zinc-400/50 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-zinc-950",
        "disabled:pointer-events-none disabled:opacity-50",
        className
      )}
      ref={buttonRef}
      type="button"
      {...props}
    >
      {children}
      <span
        aria-hidden="true"
        className={cn(
          "flex items-center gap-1 pb-0.5 transition-opacity duration-150 ease-out",
          !platformKnown && "opacity-0"
        )}
      >
        {parsed.modifiers.map((modifier) => (
          <Keycap
            key={modifier}
            label={labels[modifier]}
            pressed={held.modifiers.includes(modifier)}
          />
        ))}
        <Keycap label={keyLabel(parsed.key)} pressed={held.key} />
      </span>
    </button>
  );
}

lib/utils.ts

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

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

demo.tsx

export { default as default } from "@/import-examples/command-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;
    --color-zinc-50: oklch(98.5% 0 none);
    --color-zinc-100: oklch(96.7% 0.001 286.375);
    --color-zinc-200: oklch(92% 0.004 286.32);
    --color-zinc-300: oklch(87.1% 0.006 286.286);
    --color-zinc-400: oklch(70.5% 0.015 286.067);
    --color-zinc-600: oklch(44.2% 0.017 285.786);
    --color-zinc-700: oklch(37% 0.013 285.805);
    --color-zinc-800: oklch(27.4% 0.006 286.033);
    --color-zinc-900: oklch(21% 0.006 285.885);
    --color-zinc-950: oklch(14.1% 0.005 285.823);
    --color-white: #fff;
    --spacing: 0.25rem;
    --text-sm: 0.875rem;
    --text-sm--line-height: calc(1.25 / 0.875);
    --font-weight-medium: 500;
    --radius-md: calc(var(--radius) - 2px);
    --ease-out: cubic-bezier(0, 0, 0.2, 1);
    --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 {
  .invisible {
    visibility: hidden;
  }
  .flex {
    display: flex;
  }
  .inline-flex {
    display: inline-flex;
  }
  .h-6 {
    height: calc(var(--spacing) * 6);
  }
  .h-10 {
    height: calc(var(--spacing) * 10);
  }
  .min-w-6 {
    min-width: calc(var(--spacing) * 6);
  }
  .translate-y-0\.5 {
    --tw-translate-y: calc(var(--spacing) * 0.5);
    translate: var(--tw-translate-x) var(--tw-translate-y);
  }
  .cursor-pointer {
    cursor: pointer;
  }
  .flex-wrap {
    flex-wrap: wrap;
  }
  .items-center {
    align-items: center;
  }
  .justify-center {
    justify-content: center;
  }
  .gap-1 {
    gap: var(--spacing);
  }
  .gap-3 {
    gap: calc(var(--spacing) * 3);
  }
  .rounded-md {
    border-radius: var(--radius-md);
  }
  .rounded-xl {
    border-radius: calc(var(--radius) + 4px);
  }
  .border {
    border-style: var(--tw-border-style);
    border-width: 1px;
  }
  .border-zinc-200 {
    border-color: var(--color-zinc-200);
  }
  .bg-white {
    background-color: var(--color-white);
  }
  .px-1\.5 {
    padding-inline: calc(var(--spacing) * 1.5);
  }
  .py-1 {
    padding-block: var(--spacing);
  }
  .pr-2 {
    padding-right: calc(var(--spacing) * 2);
  }
  .pb-0\.5 {
    padding-bottom: calc(var(--spacing) * 0.5);
  }
  .pl-3\.5 {
    padding-left: calc(var(--spacing) * 3.5);
  }
  .font-sans {
    font-family: var(--font-sans);
  }
  .text-sm {
    font-size: var(--text-sm);
    line-height: var(--tw-leading, var(--text-sm--line-height));
  }
  .text-\[11px\] {
    font-size: 11px;
  }
  .leading-none {
    --tw-leading: 1;
    line-height: 1;
  }
  .font-medium {
    --tw-font-weight: var(--font-weight-medium);
    font-weight: var(--font-weight-medium);
  }
  .text-zinc-600 {
    color: var(--color-zinc-600);
  }
  .text-zinc-700 {
    color: var(--color-zinc-700);
  }
  .opacity-0 {
    opacity: 0%;
  }
  .shadow-\[0_2px_0_0_var\(--color-zinc-200\)\] {
    --tw-shadow: 0 2px 0 0 var(--tw-shadow-color, var(--color-zinc-200));
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
  }
  .shadow-none {
    --tw-shadow: 0 0 #0000;
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
  }
  .blur {
    --tw-blur: blur(8px);
    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,);
  }
  .transition-\[background-color\,border-color\] {
    transition-property: background-color,border-color;
    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
    transition-duration: var(--tw-duration, var(--default-transition-duration));
  }
  .transition-\[translate\,box-shadow\] {
    transition-property: translate,box-shadow;
    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-75 {
    --tw-duration: 75ms;
    transition-duration: 75ms;
  }
  .duration-150 {
    --tw-duration: 150ms;
    transition-duration: 150ms;
  }
  .ease-\[cubic-bezier\(0\.23\,1\,0\.32\,1\)\] {
    --tw-ease: cubic-bezier(0.23,1,0.32,1);
    transition-timing-function: cubic-bezier(0.23,1,0.32,1);
  }
  .ease-out {
    --tw-ease: var(--ease-out);
    transition-timing-function: var(--ease-out);
  }
  .group-active\/command\:translate-y-0\.5:is(:where(.group\/command):active *) {
    --tw-translate-y: calc(var(--spacing) * 0.5);
    translate: var(--tw-translate-x) var(--tw-translate-y);
  }
  .group-active\/command\:shadow-none:is(:where(.group\/command):active *) {
    --tw-shadow: 0 0 #0000;
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
  }
  .group-active\/command\:duration-75:is(:where(.group\/command):active *) {
    --tw-duration: 75ms;
    transition-duration: 75ms;
  }
  @media (hover: hover) {
    .hover\:border-zinc-300:hover {
      border-color: var(--color-zinc-300);
    }
    .hover\:bg-zinc-50:hover {
      background-color: var(--color-zinc-50);
    }
  }
  .focus-visible\:ring-2:focus-visible {
    --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + 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-zinc-400\/50:focus-visible {
    --tw-ring-color: color-mix(in srgb, oklch(70.5% 0.015 286.067) 50%, transparent);
    @supports (color: color-mix(in lab, red, red)) {
      --tw-ring-color: color-mix(in oklab, var(--color-zinc-400) 50%, transparent);
    }
  }
  .focus-visible\:ring-offset-2:focus-visible {
    --tw-ring-offset-width: 2px;
    --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
  }
  .focus-visible\:outline-hidden:focus-visible {
    --tw-outline-style: none;
    outline-style: none;
    @media (forced-colors: active) {
      outline: 2px solid transparent;
      outline-offset: 2px;
    }
  }
  .active\:bg-zinc-100:active {
    background-color: var(--color-zinc-100);
  }
  .disabled\:pointer-events-none:disabled {
    pointer-events: none;
  }
  .disabled\:opacity-50:disabled {
    opacity: 50%;
  }
  .dark\:border-white\/10:where(.dark, .dark *) {
    border-color: color-mix(in srgb, #fff 10%, transparent);
    @supports (color: color-mix(in lab, red, red)) {
      border-color: color-mix(in oklab, var(--color-white) 10%, transparent);
    }
  }
  .dark\:bg-zinc-800:where(.dark, .dark *) {
    background-color: var(--color-zinc-800);
  }
  .dark\:bg-zinc-900:where(.dark, .dark *) {
    background-color: var(--color-zinc-900);
  }
  .dark\:text-zinc-200:where(.dark, .dark *) {
    color: var(--color-zinc-200);
  }
  .dark\:text-zinc-300:where(.dark, .dark *) {
    color: var(--color-zinc-300);
  }
  .dark\:shadow-\[0_2px_0_0_var\(--color-zinc-950\)\]:where(.dark, .dark *) {
    --tw-shadow: 0 2px 0 0 var(--tw-shadow-color, var(--color-zinc-950));
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
  }
  @media (hover: hover) {
    .dark\:hover\:border-white\/15:where(.dark, .dark *):hover {
      border-color: color-mix(in srgb, #fff 15%, transparent);
    }
    @supports (color: color-mix(in lab, red, red)) {
      .dark\:hover\:border-white\/15:where(.dark, .dark *):hover {
        border-color: color-mix(in oklab, var(--color-white) 15%, transparent);
      }
    }
    .dark\:hover\:bg-zinc-800\/70:where(.dark, .dark *):hover {
      background-color: color-mix(in srgb, oklch(27.4% 0.006 286.033) 70%, transparent);
    }
    @supports (color: color-mix(in lab, red, red)) {
      .dark\:hover\:bg-zinc-800\/70:where(.dark, .dark *):hover {
        background-color: color-mix(in oklab, var(--color-zinc-800) 70%, transparent);
      }
    }
  }
  .dark\:focus-visible\:ring-offset-zinc-950:where(.dark, .dark *):focus-visible {
    --tw-ring-offset-color: var(--color-zinc-950);
  }
  .dark\:active\:bg-zinc-800:where(.dark, .dark *):active {
    background-color: var(--color-zinc-800);
  }
}
: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;
  --color-fd-background: #000;
  --color-fd-card: #0a0a0a;
  --color-fd-popover: #0a0a0a;
  --color-fd-border: rgb(255 255 255 / 0.14);
  --color-fd-accent: rgb(255 255 255 / 0.07);
}
:root {
  --sidebar: hsl(0 0% 98%);
  --sidebar-foreground: hsl(240 5.3% 26.1%);
  --sidebar-primary: hsl(240 5.9% 10%);
  --sidebar-primary-foreground: hsl(0 0% 98%);
  --sidebar-accent: hsl(240 4.8% 95.9%);
  --sidebar-accent-foreground: hsl(240 5.9% 10%);
  --sidebar-border: hsl(220 13% 91%);
  --sidebar-ring: hsl(217.2 91.2% 59.8%);
}
.dark {
  --sidebar: hsl(240 5.9% 10%);
  --sidebar-foreground: hsl(240 4.8% 95.9%);
  --sidebar-primary: hsl(224.3 76.3% 48%);
  --sidebar-primary-foreground: hsl(0 0% 100%);
  --sidebar-accent: hsl(240 3.7% 15.9%);
  --sidebar-accent-foreground: hsl(240 4.8% 95.9%);
  --sidebar-border: hsl(240 3.7% 15.9%);
  --sidebar-ring: hsl(217.2 91.2% 59.8%);
}
:root {
  --leaf: #1fa834;
}
.dark {
  --leaf: #3bb44b;
}
@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-border-style {
  syntax: "*";
  inherits: false;
  initial-value: solid;
}
@property --tw-leading {
  syntax: "*";
  inherits: false;
}
@property --tw-font-weight {
  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;
}
@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-duration {
  syntax: "*";
  inherits: false;
}
@property --tw-ease {
  syntax: "*";
  inherits: false;
}
@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-translate-x: 0;
      --tw-translate-y: 0;
      --tw-translate-z: 0;
      --tw-border-style: solid;
      --tw-leading: initial;
      --tw-font-weight: 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;
      --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-duration: initial;
      --tw-ease: initial;
    }
  }
}