HextaUI

Installation

Add HextaUI to a React project with the shadcn CLI, or copy the files in by hand.

Requirements

A React 19 project with Tailwind CSS v4 and a components.json. If you don't have one yet, set it up with the shadcn CLI.

pnpm dlx shadcn@latest init

Add components

Pass a component's registry URL to the CLI. It copies the source into your project, installs its dependencies, adds the theme tokens and pulls in any HextaUI components it builds on.

pnpm dlx shadcn@latest add https://hextaui.com/r/button.json

Add every component at once with:

pnpm dlx shadcn@latest add https://hextaui.com/r/all.json

The files are yours from then on. Import them from @/components/ui and edit them like any other code.

Namespace

Register HextaUI in components.json to add components by name.

components.json
{
  "registries": {
    "@hextaui": "https://hextaui.com/r/{name}.json"
  }
}
pnpm dlx shadcn@latest add @hextaui/button

Theme

Components use a few tokens on top of shadcn/ui: status colors, easing curves, a hairline border width and the keyframes they animate with. The CLI adds them for you. When copying files by hand, add this to your global CSS once.

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

@theme inline {
  --color-destructive-foreground: var(--destructive-foreground);
  --color-success: var(--success);
  --color-success-foreground: var(--success-foreground);
  --color-info: var(--info);
  --color-info-foreground: var(--info-foreground);
  --color-warning: var(--warning);
  --color-warning-foreground: var(--warning-foreground);
  --color-focus-ring: color-mix(in oklab, var(--foreground) 40%, transparent);
  --color-canvas: Canvas;
  --color-canvas-text: CanvasText;
  --color-highlight: Highlight;
  --color-highlight-text: HighlightText;
  --color-gray-text: GrayText;
  --font-weight-book: 520;
  --ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
  --ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1);
  --ease-spring: linear(0, 0.015 2%, 0.0532 4%, 0.1065 6%, 0.1686 8%, 0.2351 10%, 0.3363 13%, 0.4332 16%, 0.5495 20%, 0.648 24%, 0.7287 28%, 0.807 33%, 0.8646 38%, 0.9128 44%, 0.9447 50%, 0.968 57%, 0.9834 65%, 0.9924 74%, 0.9975 85%, 1);
  --default-border-width: var(--hairline);
  --text-xs: 0.6875rem;
  --text-sm: 0.8125rem;
  --text-base: 0.9375rem;
  --text-lg: 1.0625rem;
  --text-xl: 1.1875rem;
  --text-2xl: 1.4375rem;
  --text-3xl: 1.8125rem;
  --text-4xl: 2.1875rem;
  --text-5xl: 2.9375rem;
  --text-6xl: 3.6875rem;
  --text-7xl: 4.4375rem;
  --text-8xl: 5.9375rem;
  --text-9xl: 7.9375rem;
  --text-touch: max(16px, 1rem);
  --animate-shimmer: shimmer 1.6s ease-in-out infinite;
  --animate-skeleton-pulse: skeleton-pulse 2s ease-in-out infinite;
  --animate-button-state-in: button-state-in 220ms cubic-bezier(0.2, 0, 0, 1) both;
  --animate-button-state-out: button-state-out 220ms cubic-bezier(0.2, 0, 0, 1) both;
  --animate-button-draw-circle: button-draw 240ms cubic-bezier(0.2, 0, 0, 1) both;
  --animate-button-draw-check: button-draw 180ms cubic-bezier(0.2, 0, 0, 1) 180ms both;
  --animate-spinner-rotate: spinner-rotate 0.9s linear infinite;
  --animate-spinner-dash: spinner-dash 1.5s ease-in-out infinite;
  --animate-spinner-ticks: spinner-rotate 0.8s steps(8, end) infinite;
  --animate-spinner-pulse: spinner-pulse 1.6s ease-in-out infinite;
  --animate-progress-slide: progress-slide 1.6s cubic-bezier(0.65, 0, 0.35, 1) infinite;
  --animate-checkbox-draw: button-draw 200ms cubic-bezier(0.2, 0, 0, 1) 40ms both;
  --animate-button-shake: button-shake 320ms cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
  --animate-avatar-badge-pulse: avatar-badge-pulse 700ms cubic-bezier(0.215, 0.61, 0.355, 1) both;
  --animate-menu-blink: menu-blink 120ms steps(1, end) both;
  --animate-input-otp-wave: input-otp-wave 1.2s ease-in-out infinite;
  --animate-input-otp-pulse: input-otp-pulse 1.2s ease-in-out infinite;
  --animate-input-otp-pop: input-otp-pop 360ms cubic-bezier(0.23, 1, 0.32, 1) both;
  --animate-empty-in: empty-in 480ms cubic-bezier(0.23, 1, 0.32, 1) var(--empty-delay, 0ms) both;
  --animate-message-in: message-in 420ms linear(0, 0.015 2%, 0.0532 4%, 0.1065 6%, 0.1686 8%, 0.2351 10%, 0.3363 13%, 0.4332 16%, 0.5495 20%, 0.648 24%, 0.7287 28%, 0.807 33%, 0.8646 38%, 0.9128 44%, 0.9447 50%, 0.968 57%, 0.9834 65%, 0.9924 74%, 0.9975 85%, 1) both;
  --animate-video-player-flash: video-player-flash 650ms cubic-bezier(0.23, 1, 0.32, 1) both;
  --ease-island-expand: linear(0, 0.0063 1.5%, 0.0238 3%, 0.0558 4.8%, 0.104 6.8%, 0.2083 10.2%, 0.4514 17.5%, 0.5551 20.8%, 0.6562 24.2%, 0.7384 27.5%, 0.8035 30.5%, 0.8624 33.8%, 0.9101 37%, 0.9475 40.2%, 0.9796 44%, 1.0029 48%, 1.0187 52.5%, 1.0268 57.5%, 1.0267 66.5%, 1);
  --ease-island-snappy: linear(0, 0.0048 1.2%, 0.0217 2.8%, 0.0535 4.5%, 0.0953 6.2%, 0.1899 9.5%, 0.4093 16.2%, 0.5097 19.5%, 0.6077 23%, 0.6873 26.2%, 0.7555 29.5%, 0.8126 32.8%, 0.8628 36.2%, 0.9025 39.8%, 0.9387 44%, 0.9656 48.5%, 0.9849 53.5%, 0.9973 59%, 1.0062 72.5%, 1);
  --ease-island-collapse: linear(0, 0.0052 1.2%, 0.0194 2.5%, 0.0458 4%, 0.0797 5.5%, 0.1622 8.5%, 0.3617 15%, 0.4562 18.2%, 0.5484 21.8%, 0.629 25.2%, 0.6934 28.5%, 0.7521 32%, 0.804 35.8%, 0.846 39.5%, 0.8855 44%, 0.9182 49%, 0.944 54.5%, 0.9639 60.8%, 0.9875 75.5%, 1);
  --ease-island-exit: linear(0, 0.0052 1.2%, 0.0195 2.5%, 0.0459 4%, 0.0799 5.5%, 0.1625 8.5%, 0.3622 15%, 0.4569 18.2%, 0.5491 21.8%, 0.6297 25.2%, 0.6941 28.5%, 0.7527 32%, 0.8045 35.8%, 0.8465 39.5%, 0.8859 44%, 0.9186 49%, 0.9442 54.5%, 0.964 60.8%, 0.9876 75.5%, 1);
  --ease-island-bump: linear(0, 0.2143 1.2%, 0.4342 2.8%, 0.6144 4.2%, 0.7568 5.8%, 0.8636 7.2%, 0.9376 8.8%, 0.9818 10.2%, 0.9999 12%, 0.9682 14.8%, 0.8602 18%, 0.7183 21%, 0.2816 29.2%, 0.0771 34%, -0.058 38.5%, -0.1348 43%, -0.1613 47%, -0.155 51.7%, -0.0209 69%, 0.0192 78%, 0.0245 89%, 0);
  --ease-island-shake: linear(0, 0.2731 1.2%, 0.4629 2.2%, 0.625 3.2%, 0.7579 4.2%, 0.8612 5.2%, 0.9352 6.2%, 0.9808 7.2%, 1 8.5%, 0.9877 9.5%, 0.9535 10.5%, 0.8109 12.8%, 0.6306 14.8%, 0.0949 20%, -0.1525 23%, -0.2437 24.5%, -0.301 25.8%, -0.3674 28.5%, -0.3681 30.5%, -0.3303 32.8%, -0.2631 35%, 0.0353 43.2%, 0.1036 46.2%, 0.1355 49.2%, 0.1234 53.8%, -0.0127 64.2%, -0.0493 69.8%, -0.0461 74.8%, 0.0167 89.5%, 0.0151 97.2%, 0);
  --animate-island-bump: island-bump 480ms linear(0, 0.2143 1.2%, 0.4342 2.8%, 0.6144 4.2%, 0.7568 5.8%, 0.8636 7.2%, 0.9376 8.8%, 0.9818 10.2%, 0.9999 12%, 0.9682 14.8%, 0.8602 18%, 0.7183 21%, 0.2816 29.2%, 0.0771 34%, -0.058 38.5%, -0.1348 43%, -0.1613 47%, -0.155 51.7%, -0.0209 69%, 0.0192 78%, 0.0245 89%, 0);
  --animate-island-shake: island-shake 450ms linear(0, 0.2731 1.2%, 0.4629 2.2%, 0.625 3.2%, 0.7579 4.2%, 0.8612 5.2%, 0.9352 6.2%, 0.9808 7.2%, 1 8.5%, 0.9877 9.5%, 0.9535 10.5%, 0.8109 12.8%, 0.6306 14.8%, 0.0949 20%, -0.1525 23%, -0.2437 24.5%, -0.301 25.8%, -0.3674 28.5%, -0.3681 30.5%, -0.3303 32.8%, -0.2631 35%, 0.0353 43.2%, 0.1036 46.2%, 0.1355 49.2%, 0.1234 53.8%, -0.0127 64.2%, -0.0493 69.8%, -0.0461 74.8%, 0.0167 89.5%, 0.0151 97.2%, 0);
  --animate-island-flash: island-flash 400ms cubic-bezier(0.23, 1, 0.32, 1);
  --animate-island-check: button-draw 260ms cubic-bezier(0.23, 1, 0.32, 1) 140ms both;
  --animate-island-text-in: island-text-in 240ms cubic-bezier(0.23, 1, 0.32, 1) both;
  --animate-text-shimmer: text-shimmer 2s linear infinite;
}

:root {
  --destructive-foreground: oklch(0.985 0 0);
  --success: oklch(0.527 0.154 150);
  --success-foreground: oklch(0.985 0 0);
  --info: oklch(0.546 0.215 262);
  --info-foreground: oklch(0.985 0 0);
  --warning: oklch(0.555 0.163 49);
  --warning-foreground: oklch(0.985 0 0);
}

.dark {
  --destructive-foreground: oklch(0.205 0 0);
  --success: oklch(0.792 0.209 151.7);
  --success-foreground: oklch(0.205 0 0);
  --info: oklch(0.707 0.165 254.6);
  --info-foreground: oklch(0.205 0 0);
  --warning: oklch(0.828 0.189 84.4);
  --warning-foreground: oklch(0.205 0 0);
}

@keyframes island-bump {
  to {
    scale: 1.04;
  }
}

@keyframes island-shake {
  to {
    translate: 8px 0;
  }
}

@keyframes island-flash {
  0%, 50%, 100% {
    opacity: 0;
  }
  25%, 75% {
    opacity: 1;
  }
}

@keyframes island-text-in {
  from {
    opacity: 0;
    filter: blur(4px);
    scale: 0.96;
  }
}

@keyframes message-in {
  from {
    opacity: 0;
    translate: 0 0.5rem;
    scale: 0.96;
  }
}

@keyframes video-player-flash {
  from {
    opacity: 0;
    scale: var(--video-player-flash-from, 0.9);
  }
  20%, 55% {
    opacity: 1;
    scale: 1;
  }
  to {
    opacity: 0;
    scale: var(--video-player-flash-to, 1.08);
  }
}

@keyframes empty-in {
  from {
    opacity: 0;
    translate: 0 0.375rem;
    filter: blur(2px);
  }
}

@keyframes shimmer {
  from {
    translate: calc(-100% * var(--skeleton-dir, 1)) 0;
  }
  to {
    translate: calc(100% * var(--skeleton-dir, 1)) 0;
  }
}

@keyframes skeleton-pulse {
  0%, 100% {
    opacity: 0;
  }
  50% {
    opacity: 0.5;
  }
}

@keyframes button-state-in {
  from {
    opacity: 0;
    transform: perspective(220px) rotateX(90deg) translateY(0.75rem);
  }
  to {
    opacity: 1;
    transform: perspective(220px) rotateX(0) translateY(0);
  }
}

@keyframes button-state-out {
  from {
    opacity: 1;
    transform: perspective(220px) rotateX(0) translateY(0);
  }
  50% {
    opacity: 0;
  }
  to {
    opacity: 0;
    transform: perspective(220px) rotateX(-90deg) translateY(-0.75rem);
  }
}

@keyframes spinner-rotate {
  to {
    rotate: 360deg;
  }
}

@keyframes spinner-dash {
  0% {
    stroke-dasharray: 1 60;
    stroke-dashoffset: 0;
  }
  50% {
    stroke-dasharray: 34 60;
    stroke-dashoffset: -12;
  }
  100% {
    stroke-dasharray: 34 60;
    stroke-dashoffset: -56;
  }
}

@keyframes progress-slide {
  from {
    translate: calc(-100% * var(--progress-dir, 1)) 0;
  }
  to {
    translate: calc(250% * var(--progress-dir, 1)) 0;
  }
}

@keyframes spinner-pulse {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0.4;
  }
}

@keyframes button-draw {
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes avatar-badge-pulse {
  from {
    scale: 1;
    opacity: 0.5;
  }
  to {
    scale: 2.6;
    opacity: 0;
  }
}

@keyframes menu-blink {
  0% {
    background-color: transparent;
  }
  50%, 100% {
    background-color: var(--menu-blink, var(--color-accent));
  }
}

@keyframes input-otp-wave {
  0%, 60%, 100% {
    translate: 0 0;
    opacity: 1;
  }
  30% {
    translate: 0 -0.18em;
    opacity: 0.45;
  }
}

@keyframes input-otp-pulse {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0.45;
  }
}

@keyframes input-otp-pop {
  0% {
    scale: 1;
  }
  40% {
    scale: 1.16;
  }
  100% {
    scale: 1;
  }
}

@keyframes button-shake {
  0%, 100% {
    translate: 0;
  }
  20% {
    translate: -3px;
  }
  40% {
    translate: 3px;
  }
  60% {
    translate: -2px;
  }
  80% {
    translate: 2px;
  }
}

@keyframes text-shimmer {
  from {
    background-position: 100% 0;
  }
  to {
    background-position: -100% 0;
  }
}

@utility text-hero {
  font-size: min(calc((100vw - 1.5rem) / 15.75), 2.6875rem);
  line-height: 1.3;
}

@utility border-hairline {
  border-width: var(--hairline);
}

@utility text-shimmer {
  background-image: linear-gradient(90deg, var(--muted-foreground) 25%, var(--info) 40%, color-mix(in oklab, var(--info) 45%, white) 50%, var(--info) 60%, var(--muted-foreground) 75%);
  background-size: 250% 100%;
  background-clip: text;
  color: transparent;
  animation: var(--animate-text-shimmer);
}

:root {
  --hairline: 1px;
}

@media (min-resolution: 1.5dppx) {
  :root {
    --hairline: calc(1px / 1.5);
  }
}

@media (min-resolution: 2dppx) {
  :root {
    --hairline: 0.5px;
  }
}

@media (min-resolution: 3dppx) {
  :root {
    --hairline: calc(1px / 3);
  }
}

@media (min-resolution: 4dppx) {
  :root {
    --hairline: 0.25px;
  }
}

@layer base {
  body {
    position: relative;
  }
  :where(a, button, input, label, select, summary, textarea, [role="button"]) {
    touch-action: manipulation;
  }
}

AI assistants

Every page is available as Markdown, so assistants get the full source, examples and API without scraping. Use the Copy page menu at the top of any page, or point your assistant at these files.

  • https://hextaui.com/llms.txt lists every page with a short description.
  • https://hextaui.com/llms-full.txt has every page in one file.
  • Add .md to any docs URL for that page, like https://hextaui.com/docs/button.md.

For the best results, connect the HextaUI MCP server. Assistants search the docs, read each API and get install commands without leaving your editor.

With the namespace above, the shadcn MCP server lets assistants like Claude Code, Cursor and Codex browse and install HextaUI components directly.

pnpm dlx shadcn@latest mcp init --client claude