HextaUI

Instalação

Adicione o HextaUI a um projeto React com a shadcn CLI, ou copie os arquivos manualmente.

Requisitos

Um projeto React 19 com Tailwind CSS v4 e um components.json. Se você ainda não tem um, configure-o com a shadcn CLI.

pnpm dlx shadcn@latest init

Adicionar componentes

Passe a URL do registry de um componente à CLI. Ela copia o código-fonte para o seu projeto, instala suas dependências, adiciona os tokens do tema e traz os componentes do HextaUI em que ele se baseia.

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

Adicione todos os componentes de uma vez com:

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

A partir daí, os arquivos são seus. Importe-os de @/components/ui e edite-os como qualquer outro código.

Namespace

Registre o HextaUI em components.json para adicionar componentes pelo nome.

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

Tema

Os componentes usam alguns tokens além do shadcn/ui: cores de status, curvas de easing, uma largura de borda hairline e os keyframes com que animam. A CLI os adiciona para você. Ao copiar os arquivos manualmente, adicione isto ao seu CSS global uma única vez.

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;
  --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 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;
  }
}

Assistentes de IA

Todas as páginas estão disponíveis em Markdown, então os assistentes recebem o código-fonte completo, os exemplos e a API sem precisar fazer scraping. Use o menu Copy page no topo de qualquer página, ou aponte seu assistente para estes arquivos.

  • https://hextaui.com/llms.txt lista todas as páginas com uma descrição curta.
  • https://hextaui.com/llms-full.txt tem todas as páginas em um único arquivo.
  • Adicione .md a qualquer URL da documentação para obter aquela página, como https://hextaui.com/docs/button.md.

Para melhores resultados, conecte o servidor MCP do HextaUI. Os assistentes pesquisam a documentação, leem cada API e obtêm comandos de instalação sem sair do seu editor.

Com o namespace acima, o servidor MCP do shadcn permite que assistentes como Claude Code, Cursor e Codex naveguem e instalem componentes do HextaUI diretamente.

pnpm dlx shadcn@latest mcp init --client claude