# Vesper > Vesper is Tenstorrent's design system for the web: a React component library built on a shared set of design tokens, with light and dark themes, an icon set, and first-class Tailwind support. The full text of every Vesper documentation page. For an index of links instead, see https://vesper.tenstorrent.com/llms.txt --- Source: https://vesper.tenstorrent.com/getting-started.md # Getting Started ## Install You can install the `@tenstorrent/vesper` package from the npm registry using your package manager of choice: ```sh yarn add @tenstorrent/vesper npm install @tenstorrent/vesper pnpm add @tenstorrent/vesper ``` Since `@tenstorrent/vesper` is a React package, you will also need to have `react@^19.0.0` and `react-dom@^19.0.0` installed, as they are peer dependencies. ```sh # install peer deps (skip if these are already installed) npm install --save-peer react@^19.0.0 react-dom@^19.0.0 ``` ### Import styles Prior to using any components, you will need to import the library styles from `@tenstorrent/vesper/styles.css` somewhere at the top-level of your application. For example, in a `Next.js` application, you may import them in your app's root `layout.tsx` file: ```tsx import type { ReactNode } from "react"; import "@tenstorrent/vesper/styles.css"; export default function RootLayout({ children }: { children: ReactNode }) { return ( {children} ); } ``` This makes the Vesper library's component styles and css tokens globally available in your application. For more information on usage of Vesper's css tokens, see [CSS token usage](#css-token-usage). If you are using [Tailwind](https://tailwindcss.com) to style your app, we supply a Tailwind-specific file you can import instead. This has two advantages: 1. Vesper's component styles get injected into Tailwind's `components` layer, which lets Tailwind utility classes override vesper css classes. 2. Vesper's css tokens get injected into the Tailwind `@theme` so you can use them in Tailwind utility classes, like `bg-vesper-purple-300`, for example. You should import Vesper's Tailwind styles **after** importing `tailwindcss` in your css file, like so: ```css @import "tailwindcss"; @import "@tenstorrent/vesper/tailwind.css"; ``` If you import Vesper's Tailwind styles before importing `tailwindcss`, the order of Tailwind's internal css layers will get mangled. ### Fonts Vesper components are styled using two fonts, `Inter Tight` and `IBM Plex Mono`. The fonts themselves are **not** bundled with the package, only the font stacks are. If you want Vesper's components to render in `Inter Tight` and `IBM Plex Mono`, you need to load those fonts in your app (for example with `next/font/google` if developing a `Next.js` application, a `@font-face` rule, or a `` to Google Fonts). If they aren't loaded, the fallbacks in each stack are used. The easiest way to do this is to embed the corresponding Google Fonts code into the `` of your html: ```html ``` --- Source: https://vesper.tenstorrent.com/tokens.md # CSS Tokens Vesper is built on top of a set of design tokens corresponding to color, spacing, radius, leading, tracking, shadow, stroke, and background. These tokens are exposed in your app as CSS variables when you [import the component styles](https://vesper.tenstorrent.com/getting-started.md#import-styles), and can also be used in Tailwind utilities. ## Colors Color tokens are split into _primitive_ and _semantic_ tokens. **Primitive** tokens are the raw palette values from our design system. **Semantic** tokens are aliases of primitive tokens intended to be more ergonomic for applied use (these will be familiar to you if you've used a library like Tailwind). For example, the semantic token `--vesper-background-primary` aliases the primitive `--vesper-stone-0` color token value. We prefer using semantic tokens wherever possible, since they automatically resolve to the correct primitive for the active theme. ### Primitive color tokens | Preview | CSS variable | Light mode value | Dark mode value | | ---------------------------------------- | ----------------------------- | --------------------------- | ---------------------------- | | | `--vesper-static-black` | `#071614` | `#071614` | | | `--vesper-static-white` | `#f1f8f8` | `#f1f8f8` | | | `--vesper-transparent` | `transparent` | `transparent` | | | `--vesper-stone-0` | `#f8fcfb` | `#050f0e` | | | `--vesper-stone-50` | `#eef6f6` | `#0b1817` | | | `--vesper-stone-100` | `#e8f2f1` | `#13201e` | | | `--vesper-stone-200` | `#dfecea` | `#1b2826` | | | `--vesper-stone-300` | `#d0e1e0` | `#233431` | | | `--vesper-stone-400` | `#bdd1cf` | `#2d4340` | | | `--vesper-stone-500` | `#a2b9b7` | `#395651` | | | `--vesper-stone-600` | `#718e8c` | `#5e7d78` | | | `--vesper-stone-700` | `#51706d` | `#7d9b97` | | | `--vesper-stone-800` | `#344b49` | `#b3ccc8` | | | `--vesper-stone-900` | `#050f0e` | `#e7f0ee` | | | `--vesper-teal-0` | `#f7fbfd` | `#05181e` | | | `--vesper-teal-50` | `#e7f7fd` | `#0a2229` | | | `--vesper-teal-100` | `#d3eff8` | `#0e303a` | | | `--vesper-teal-200` | `#aae5f8` | `#073e4f` | | | `--vesper-teal-300` | `#8ad2ea` | `#0d4659` | | | `--vesper-teal-400` | `#5cbcdb` | `#0f546b` | | | `--vesper-teal-500` | `#32abd2` | `#136b86` | | | `--vesper-teal-600` | `#1b8eb1` | `#1a8fb2` | | | `--vesper-teal-700` | `#176782` | `#46afd2` | | | `--vesper-teal-800` | `#144a5d` | `#7dcde8` | | | `--vesper-teal-900` | `#0c2f3b` | `#a3e0f5` | | | `--vesper-purple-0` | `#f8f7fd` | `#0f0e1b` | | | `--vesper-purple-50` | `#efedfd` | `#141127` | | | `--vesper-purple-100` | `#dedafb` | `#1d1938` | | | `--vesper-purple-200` | `#c9c2fa` | `#27214a` | | | `--vesper-purple-300` | `#a89eea` | `#2f285d` | | | `--vesper-purple-400` | `#7e71d0` | `#382f74` | | | `--vesper-purple-500` | `#6458b6` | `#5145a1` | | | `--vesper-purple-600` | `#4d429a` | `#7e71d0` | | | `--vesper-purple-700` | `#342b6e` | `#9c91e3` | | | `--vesper-purple-800` | `#272150` | `#b4a8ff` | | | `--vesper-purple-900` | `#1d1b2d` | `#cdc6fa` | | | `--vesper-mint-0` | `#f1fdfb` | `#041f19` | | | `--vesper-mint-50` | `#e3fcf9` | `#073128` | | | `--vesper-mint-100` | `#d2f4ef` | `#0b4137` | | | `--vesper-mint-200` | `#b6ede2` | `#0b5144` | | | `--vesper-mint-300` | `#7cdecc` | `#116959` | | | `--vesper-mint-400` | `#40bfa8` | `#15937c` | | | `--vesper-mint-500` | `#29a38d` | `#1bbb9e` | | | `--vesper-mint-600` | `#227767` | `#39c6ac` | | | `--vesper-mint-700` | `#13584b` | `#4ce5c9` | | | `--vesper-mint-800` | `#0f3d35` | `#82edd9` | | | `--vesper-mint-900` | `#112c26` | `#caf7f1` | | | `--vesper-pink-0` | `#fdf7fb` | `#1f0a17` | | | `--vesper-pink-50` | `#fdedf7` | `#2e0f22` | | | `--vesper-pink-100` | `#fcd9ef` | `#3d152e` | | | `--vesper-pink-200` | `#ffc2e8` | `#491d38` | | | `--vesper-pink-300` | `#f17ec5` | `#6b2450` | | | `--vesper-pink-400` | `#e557af` | `#91306c` | | | `--vesper-pink-500` | `#be378a` | `#be378a` | | | `--vesper-pink-600` | `#91306c` | `#e34faa` | | | `--vesper-pink-700` | `#6b2450` | `#f17ec5` | | | `--vesper-pink-800` | `#441d35` | `#ffc2e8` | | | `--vesper-pink-900` | `#2e0f22` | `#fde2f3` | | | `--vesper-yellow-0` | `#fff7e5` | `#211908` | | | `--vesper-yellow-50` | `#fff2d6` | `#31260c` | | | `--vesper-yellow-100` | `#ffeabd` | `#3f3112` | | | `--vesper-yellow-200` | `#ffe0a3` | `#534018` | | | `--vesper-yellow-300` | `#ffca61` | `#7a5c1a` | | | `--vesper-yellow-400` | `#ffb71b` | `#9c7217` | | | `--vesper-yellow-500` | `#d99a12` | `#d09311` | | | `--vesper-yellow-600` | `#986f16` | `#ffb71b` | | | `--vesper-yellow-700` | `#6d5217` | `#ffc757` | | | `--vesper-yellow-800` | `#473715` | `#ffd88a` | | | `--vesper-yellow-900` | `#2e2614` | `#ffeecc` | | | `--vesper-green-0` | `#f1fdf8` | `#041f15` | | | `--vesper-green-50` | `#e3fcf4` | `#073120` | | | `--vesper-green-100` | `#d2f4e9` | `#0b412e` | | | `--vesper-green-200` | `#b1ecd6` | `#0b5137` | | | `--vesper-green-300` | `#78ddb8` | `#106043` | | | `--vesper-green-400` | `#40bf90` | `#128159` | | | `--vesper-green-500` | `#29a376` | `#19ae77` | | | `--vesper-green-600` | `#227758` | `#38c28f` | | | `--vesper-green-700` | `#13583f` | `#4ce5ad` | | | `--vesper-green-800` | `#0f3d2c` | `#82edc6` | | | `--vesper-green-900` | `#112c21` | `#caf7e9` | | | `--vesper-red-0` | `#feebe7` | `#1d0f0c` | | | `--vesper-red-50` | `#fcdfd9` | `#2b130d` | | | `--vesper-red-100` | `#fccac0` | `#3d170f` | | | `--vesper-red-200` | `#f99a86` | `#5c180a` | | | `--vesper-red-300` | `#f56447` | `#85230f` | | | `--vesper-red-400` | `#ef3f1a` | `#af290e` | | | `--vesper-red-500` | `#cb2c0b` | `#cb2c0b` | | | `--vesper-red-600` | `#9b2208` | `#f04b28` | | | `--vesper-red-700` | `#6e1d0c` | `#f37358` | | | `--vesper-red-800` | `#531609` | `#f99b86` | | | `--vesper-red-900` | `#2f140e` | `#fac6b7` | | | `--vesper-amber-0` | `#fff4e5` | `#211608` | | | `--vesper-amber-50` | `#ffedd6` | `#31210c` | | | `--vesper-amber-100` | `#ffe4c2` | `#432b0e` | | | `--vesper-amber-200` | `#ffd4a3` | `#523614` | | | `--vesper-amber-300` | `#ffba6b` | `#8a530f` | | | `--vesper-amber-400` | `#ffa033` | `#bb690c` | | | `--vesper-amber-500` | `#ed881d` | `#ed881d` | | | `--vesper-amber-600` | `#a8610b` | `#ffa333` | | | `--vesper-amber-700` | `#8d5107` | `#ffb35c` | | | `--vesper-amber-800` | `#523614` | `#ffcf99` | | | `--vesper-amber-900` | `#35230d` | `#ffe7cc` | | | `--vesper-sky-0` | `#f5fbfe` | `#09101b` | | | `--vesper-sky-50` | `#e5f5ff` | `#0f1b2d` | | | `--vesper-sky-100` | `#d0e9fb` | `#10243e` | | | `--vesper-sky-200` | `#acd7f6` | `#092e53` | | | `--vesper-sky-300` | `#7ebeed` | `#0f4d86` | | | `--vesper-sky-400` | `#55aae7` | `#135ea7` | | | `--vesper-sky-500` | `#2375c7` | `#1671c9` | | | `--vesper-sky-600` | `#1059a2` | `#0091ff` | | | `--vesper-sky-700` | `#134272` | `#33a3ff` | | | `--vesper-sky-800` | `#113355` | `#70b8ff` | | | `--vesper-sky-900` | `#122130` | `#c2e0ff` | | | `--vesper-alpha-stone-0` | `rgba(28, 161, 157, 0.023)` | `rgba(33, 60, 61, 0.03)` | | | `--vesper-alpha-stone-50` | `rgba(14, 129, 115, 0.043)` | `rgba(154, 246, 244, 0.035)` | | | `--vesper-alpha-stone-100` | `rgba(19, 124, 99, 0.074)` | `rgba(184, 244, 243, 0.075)` | | | `--vesper-alpha-stone-200` | `rgba(38, 126, 112, 0.125)` | `rgba(204, 245, 241, 0.11)` | | | `--vesper-alpha-stone-300` | `rgba(15, 97, 87, 0.169)` | `rgba(184, 245, 233, 0.161)` | | | `--vesper-alpha-stone-400` | `rgba(8, 78, 75, 0.255)` | `rgba(168, 225, 217, 0.247)` | | | `--vesper-alpha-stone-500` | `rgba(3, 58, 56, 0.345)` | `rgba(144, 213, 203, 0.357)` | | | `--vesper-alpha-stone-600` | `rgba(7, 60, 57, 0.572)` | `rgba(195, 255, 247, 0.461)` | | | `--vesper-alpha-stone-700` | `rgba(17, 59, 55, 0.729)` | `rgba(207, 252, 246, 0.591)` | | | `--vesper-alpha-stone-800` | `rgba(14, 42, 39, 0.839)` | `rgba(212, 242, 237, 0.834)` | | | `--vesper-alpha-stone-900` | `rgba(0, 15, 15, 0.973)` | `rgba(245, 255, 253, 0.937)` | | | `--vesper-alpha-contrast-0` | `transparent` | `transparent` | | | `--vesper-alpha-contrast-50` | `rgba(7, 22, 20, 0.05)` | `rgba(241, 248, 248, 0.05)` | | | `--vesper-alpha-contrast-100` | `rgba(7, 22, 20, 0.1)` | `rgba(241, 248, 248, 0.1)` | | | `--vesper-alpha-contrast-200` | `rgba(7, 22, 20, 0.2)` | `rgba(241, 248, 248, 0.2)` | | | `--vesper-alpha-contrast-300` | `rgba(7, 22, 20, 0.3)` | `rgba(241, 248, 248, 0.3)` | | | `--vesper-alpha-contrast-400` | `rgba(7, 22, 20, 0.4)` | `rgba(241, 248, 248, 0.4)` | | | `--vesper-alpha-contrast-500` | `rgba(7, 22, 20, 0.5)` | `rgba(241, 248, 248, 0.5)` | | | `--vesper-alpha-contrast-600` | `rgba(7, 22, 20, 0.6)` | `rgba(241, 248, 248, 0.6)` | | | `--vesper-alpha-contrast-700` | `rgba(7, 22, 20, 0.7)` | `rgba(241, 248, 248, 0.7)` | | | `--vesper-alpha-contrast-800` | `rgba(7, 22, 20, 0.8)` | `rgba(241, 248, 248, 0.8)` | | | `--vesper-alpha-contrast-900` | `rgba(7, 22, 20, 0.9)` | `rgba(241, 248, 248, 0.9)` | | | `--vesper-alpha-inverse-0` | `transparent` | `transparent` | | | `--vesper-alpha-inverse-50` | `rgba(241, 248, 248, 0.05)` | `rgba(7, 22, 20, 0.05)` | | | `--vesper-alpha-inverse-100` | `rgba(241, 248, 248, 0.1)` | `rgba(7, 22, 20, 0.1)` | | | `--vesper-alpha-inverse-200` | `rgba(241, 248, 248, 0.2)` | `rgba(7, 22, 20, 0.2)` | | | `--vesper-alpha-inverse-300` | `rgba(241, 248, 248, 0.3)` | `rgba(7, 22, 20, 0.3)` | | | `--vesper-alpha-inverse-400` | `rgba(241, 248, 248, 0.4)` | `rgba(7, 22, 20, 0.4)` | | | `--vesper-alpha-inverse-500` | `rgba(241, 248, 248, 0.5)` | `rgba(7, 22, 20, 0.5)` | | | `--vesper-alpha-inverse-600` | `rgba(241, 248, 248, 0.6)` | `rgba(7, 22, 20, 0.6)` | | | `--vesper-alpha-inverse-700` | `rgba(241, 248, 248, 0.7)` | `rgba(7, 22, 20, 0.7)` | | | `--vesper-alpha-inverse-800` | `rgba(241, 248, 248, 0.8)` | `rgba(7, 22, 20, 0.8)` | | | `--vesper-alpha-inverse-900` | `rgba(241, 248, 248, 0.9)` | `rgba(7, 22, 20, 0.9)` | | | `--vesper-alpha-black-0` | `transparent` | `transparent` | | | `--vesper-alpha-black-50` | `rgba(7, 22, 20, 0.05)` | `rgba(7, 22, 20, 0.05)` | | | `--vesper-alpha-black-100` | `rgba(7, 22, 20, 0.1)` | `rgba(7, 22, 20, 0.1)` | | | `--vesper-alpha-black-200` | `rgba(7, 22, 20, 0.2)` | `rgba(7, 22, 20, 0.2)` | | | `--vesper-alpha-black-300` | `rgba(7, 22, 20, 0.3)` | `rgba(7, 22, 20, 0.3)` | | | `--vesper-alpha-black-400` | `rgba(7, 22, 20, 0.4)` | `rgba(7, 22, 20, 0.4)` | | | `--vesper-alpha-black-500` | `rgba(7, 22, 20, 0.5)` | `rgba(7, 22, 20, 0.5)` | | | `--vesper-alpha-black-600` | `rgba(7, 22, 20, 0.6)` | `rgba(7, 22, 20, 0.6)` | | | `--vesper-alpha-black-700` | `rgba(7, 22, 20, 0.7)` | `rgba(7, 22, 20, 0.7)` | | | `--vesper-alpha-black-800` | `rgba(7, 22, 20, 0.8)` | `rgba(7, 22, 20, 0.8)` | | | `--vesper-alpha-black-900` | `rgba(7, 22, 20, 0.9)` | `rgba(7, 22, 20, 0.9)` | | | `--vesper-alpha-white-0` | `transparent` | `transparent` | | | `--vesper-alpha-white-50` | `rgba(241, 248, 248, 0.05)` | `rgba(241, 248, 248, 0.05)` | | | `--vesper-alpha-white-100` | `rgba(241, 248, 248, 0.1)` | `rgba(241, 248, 248, 0.1)` | | | `--vesper-alpha-white-200` | `rgba(241, 248, 248, 0.2)` | `rgba(241, 248, 248, 0.2)` | | | `--vesper-alpha-white-300` | `rgba(241, 248, 248, 0.3)` | `rgba(241, 248, 248, 0.3)` | | | `--vesper-alpha-white-400` | `rgba(241, 248, 248, 0.4)` | `rgba(241, 248, 248, 0.4)` | | | `--vesper-alpha-white-500` | `rgba(241, 248, 248, 0.5)` | `rgba(241, 248, 248, 0.5)` | | | `--vesper-alpha-white-600` | `rgba(241, 248, 248, 0.6)` | `rgba(241, 248, 248, 0.6)` | | | `--vesper-alpha-white-700` | `rgba(241, 248, 248, 0.7)` | `rgba(241, 248, 248, 0.7)` | | | `--vesper-alpha-white-800` | `rgba(241, 248, 248, 0.8)` | `rgba(241, 248, 248, 0.8)` | | | `--vesper-alpha-white-900` | `rgba(241, 248, 248, 0.9)` | `rgba(241, 248, 248, 0.9)` | ### Semantic color tokens | CSS variable | Alias | | ----------------------------------------- | ----------------------------- | | `--vesper-background-primary` | `--vesper-stone-0` | | `--vesper-background-secondary` | `--vesper-stone-50` | | `--vesper-background-tertiary` | `--vesper-stone-100` | | `--vesper-background-disabled` | `--vesper-alpha-stone-50` | | `--vesper-background-inverse-primary` | `--vesper-alpha-stone-900` | | `--vesper-background-inverse-secondary` | `--vesper-alpha-stone-800` | | `--vesper-background-inverse-disabled` | `--vesper-alpha-inverse-50` | | `--vesper-background-accent-subtle` | `--vesper-teal-50` | | `--vesper-background-accent-base` | `--vesper-teal-400` | | `--vesper-background-success-subtle` | `--vesper-green-50` | | `--vesper-background-success-base` | `--vesper-green-600` | | `--vesper-background-warning-subtle` | `--vesper-amber-0` | | `--vesper-background-warning-base` | `--vesper-amber-600` | | `--vesper-background-error-subtle` | `--vesper-red-0` | | `--vesper-background-error-base` | `--vesper-red-500` | | `--vesper-background-info-subtle` | `--vesper-sky-50` | | `--vesper-background-info-base` | `--vesper-sky-600` | | `--vesper-background-static-white` | `--vesper-static-white` | | `--vesper-background-static-black` | `--vesper-static-black` | | `--vesper-background-brand-purple-subtle` | `--vesper-purple-100` | | `--vesper-background-brand-purple-base` | `--vesper-purple-600` | | `--vesper-background-brand-mint-subtle` | `--vesper-mint-50` | | `--vesper-background-brand-mint-base` | `--vesper-mint-600` | | `--vesper-background-brand-pink-subtle` | `--vesper-pink-50` | | `--vesper-background-brand-pink-base` | `--vesper-pink-600` | | `--vesper-background-brand-yellow-subtle` | `--vesper-yellow-50` | | `--vesper-background-brand-yellow-base` | `--vesper-yellow-400` | | `--vesper-text-primary` | `--vesper-stone-900` | | `--vesper-text-secondary` | `--vesper-stone-800` | | `--vesper-text-tertiary` | `--vesper-stone-700` | | `--vesper-text-disabled` | `--vesper-alpha-stone-500` | | `--vesper-text-inverse-primary` | `--vesper-stone-0` | | `--vesper-text-inverse-secondary` | `--vesper-stone-300` | | `--vesper-text-inverse-tertiary` | `--vesper-stone-500` | | `--vesper-text-alpha-disabled` | `--vesper-alpha-inverse-300` | | `--vesper-text-on-color` | `--vesper-static-white` | | `--vesper-text-accent` | `--vesper-teal-700` | | `--vesper-text-success` | `--vesper-mint-600` | | `--vesper-text-warning` | `--vesper-amber-700` | | `--vesper-text-error` | `--vesper-red-600` | | `--vesper-text-info` | `--vesper-sky-600` | | `--vesper-text-static-black` | `--vesper-static-black` | | `--vesper-text-static-white` | `--vesper-static-white` | | `--vesper-text-link-default` | `--vesper-teal-700` | | `--vesper-text-link-hover` | `--vesper-teal-600` | | `--vesper-text-link-visited` | `--vesper-teal-800` | | `--vesper-text-link-disabled` | `--vesper-alpha-stone-400` | | `--vesper-text-brand-purple` | `--vesper-purple-700` | | `--vesper-text-brand-mint` | `--vesper-mint-600` | | `--vesper-text-brand-pink` | `--vesper-pink-600` | | `--vesper-text-brand-yellow` | `--vesper-yellow-600` | | `--vesper-icon-primary` | `--vesper-stone-900` | | `--vesper-icon-secondary` | `--vesper-stone-800` | | `--vesper-icon-tertiary` | `--vesper-stone-700` | | `--vesper-icon-disabled` | `--vesper-alpha-stone-500` | | `--vesper-icon-inverse-primary` | `--vesper-stone-0` | | `--vesper-icon-inverse-secondary` | `--vesper-stone-300` | | `--vesper-icon-inverse-tertiary` | `--vesper-stone-500` | | `--vesper-icon-inverse-disabled` | `--vesper-alpha-inverse-300` | | `--vesper-icon-on-color` | `--vesper-static-white` | | `--vesper-icon-accent` | `--vesper-teal-700` | | `--vesper-icon-success` | `--vesper-mint-600` | | `--vesper-icon-warning` | `--vesper-amber-700` | | `--vesper-icon-error` | `--vesper-red-600` | | `--vesper-icon-info` | `--vesper-sky-600` | | `--vesper-icon-static-black` | `--vesper-static-black` | | `--vesper-icon-static-white` | `--vesper-static-white` | | `--vesper-icon-brand-purple` | `--vesper-purple-700` | | `--vesper-icon-brand-mint` | `--vesper-mint-600` | | `--vesper-icon-brand-pink` | `--vesper-pink-600` | | `--vesper-icon-brand-yellow` | `--vesper-yellow-600` | | `--vesper-border-primary` | `--vesper-stone-400` | | `--vesper-border-secondary` | `--vesper-stone-300` | | `--vesper-border-tertiary` | `--vesper-stone-200` | | `--vesper-border-strong` | `--vesper-stone-900` | | `--vesper-border-disabled` | `--vesper-alpha-stone-200` | | `--vesper-border-focus` | `--vesper-teal-200` | | `--vesper-border-inverse-primary` | `--vesper-stone-400` | | `--vesper-border-inverse-secondary` | `--vesper-stone-600` | | `--vesper-border-inverse-strong` | `--vesper-stone-0` | | `--vesper-border-inverse-disabled` | `--vesper-alpha-inverse-100` | | `--vesper-border-inverse-focus` | `--vesper-teal-500` | | `--vesper-border-static-white` | `--vesper-static-white` | | `--vesper-border-static-black` | `--vesper-static-black` | | `--vesper-border-accent-subtle` | `--vesper-teal-200` | | `--vesper-border-accent-base` | `--vesper-teal-600` | | `--vesper-border-success-subtle` | `--vesper-green-200` | | `--vesper-border-success-base` | `--vesper-green-500` | | `--vesper-border-warning-subtle` | `--vesper-amber-200` | | `--vesper-border-warning-base` | `--vesper-amber-500` | | `--vesper-border-error-subtle` | `--vesper-red-200` | | `--vesper-border-error-base` | `--vesper-red-500` | | `--vesper-border-info-subtle` | `--vesper-sky-200` | | `--vesper-border-info-base` | `--vesper-sky-500` | | `--vesper-border-brand-purple-subtle` | `--vesper-purple-200` | | `--vesper-border-brand-purple-base` | `--vesper-purple-500` | | `--vesper-border-brand-mint-subtle` | `--vesper-mint-200` | | `--vesper-border-brand-mint-base` | `--vesper-mint-500` | | `--vesper-border-brand-pink-subtle` | `--vesper-pink-200` | | `--vesper-border-brand-pink-base` | `--vesper-pink-500` | | `--vesper-border-brand-yellow-subtle` | `--vesper-yellow-200` | | `--vesper-border-brand-yellow-base` | `--vesper-yellow-500` | | `--vesper-scrim-contrast-subtle` | `--vesper-alpha-black-300` | | `--vesper-scrim-contrast-base` | `--vesper-alpha-black-600` | | `--vesper-scrim-inverse-subtle` | `--vesper-alpha-white-300` | | `--vesper-scrim-inverse-base` | `--vesper-alpha-white-600` | | `--vesper-state-neutral-hover` | `--vesper-alpha-stone-50` | | `--vesper-state-neutral-active` | `--vesper-alpha-stone-100` | | `--vesper-state-contrast-hover` | `--vesper-alpha-contrast-50` | | `--vesper-state-contrast-active` | `--vesper-alpha-contrast-100` | | `--vesper-state-inverse-hover` | `--vesper-alpha-inverse-100` | | `--vesper-state-inverse-active` | `--vesper-alpha-inverse-200` | | `--vesper-tint-neutral-100` | `--vesper-alpha-stone-50` | | `--vesper-tint-neutral-200` | `--vesper-alpha-stone-100` | | `--vesper-tint-neutral-300` | `--vesper-alpha-stone-200` | | `--vesper-tint-neutral-400` | `--vesper-alpha-stone-300` | | `--vesper-tint-contrast-100` | `--vesper-alpha-contrast-50` | | `--vesper-tint-contrast-200` | `--vesper-alpha-contrast-100` | | `--vesper-tint-contrast-300` | `--vesper-alpha-contrast-200` | | `--vesper-tint-contrast-400` | `--vesper-alpha-contrast-300` | | `--vesper-tint-inverse-100` | `--vesper-alpha-inverse-300` | | `--vesper-tint-inverse-200` | `--vesper-alpha-inverse-500` | | `--vesper-tint-inverse-300` | `--vesper-alpha-inverse-700` | | `--vesper-tint-inverse-400` | `--vesper-alpha-inverse-800` | ### Tailwind color classes If you imported `@tenstorrent/vesper/tailwind.css` into your css file where you set up Tailwind, these color tokens are available for use in Tailwind's utility classes. Every color token (primitive and semantic) is registered in Tailwind's `--color-*` namespace as `--color-vesper-*`, so any Tailwind utility that accepts a color works: `bg-*`, `text-*`, `border-*`, `outline-*`, `ring-*`, `fill-*`, `stroke-*`, `divide-*`, `shadow-*`, `accent-*`, and so on. For example: ```tsx
;
;
; ``` ## Fonts | CSS variable | Value | | -------------------- | -------------------------------------- | | `--vesper-font-sans` | `"Inter Tight", system-ui, sans-serif` | | `--vesper-font-mono` | `"IBM Plex Mono", monospace` | ### Tailwind font classes Font tokens are registered in Tailwind's `--font-*` namespace, so they are available as `font-family` utilities: ```tsx

; ; ``` ## Leading Leading (line height) tokens are percentage-based, so they scale with the element's font size. | CSS variable | Value | | -------------------------- | ------ | | `--vesper-leading-none` | `100%` | | `--vesper-leading-tight` | `110%` | | `--vesper-leading-snug` | `120%` | | `--vesper-leading-normal` | `140%` | | `--vesper-leading-relaxed` | `150%` | | `--vesper-leading-loose` | `175%` | ### Tailwind leading classes Leading tokens are registered in Tailwind's `--leading-*` namespace, so they are available as `line-height` utilities: ```tsx

;

; ``` ## Tracking Tracking (letter spacing) tokens are also relative to the element's font size. The values below are expressed in `em`, so they scale with the font size — for example, `--vesper-tracking-tight` resolves to `-0.25px` at a `16px` font size. | CSS variable | Value | Equivalent at `16px` font size | | ---------------------------- | ------------- | ------------------------------ | | `--vesper-tracking-tightest` | `-0.046875em` | `-0.75px` | | `--vesper-tracking-tighter` | `-0.03125em` | `-0.5px` | | `--vesper-tracking-tight` | `-0.015625em` | `-0.25px` | | `--vesper-tracking-normal` | `0em` | `0px` | | `--vesper-tracking-wide` | `0.015625em` | `0.25px` | | `--vesper-tracking-wider` | `0.03125em` | `0.5px` | | `--vesper-tracking-widest` | `0.046875em` | `0.75px` | ### Tailwind tracking classes Tracking tokens are registered in Tailwind's `--tracking-*` namespace, so they are available as `letter-spacing` utilities: ```tsx

; ; ``` ## Radius | CSS variable | Value | Equivalent at `16px` root font size | | ---------------------- | ---------- | ----------------------------------- | | `--vesper-radius-half` | `0.125rem` | `2px` | | `--vesper-radius-1` | `0.25rem` | `4px` | | `--vesper-radius-2` | `0.5rem` | `8px` | | `--vesper-radius-3` | `0.75rem` | `12px` | | `--vesper-radius-4` | `1rem` | `16px` | | `--vesper-radius-6` | `1.5rem` | `24px` | | `--vesper-radius-7` | `1.75rem` | `28px` | ### Tailwind radius classes Radius tokens are registered in Tailwind's `--radius-*` namespace, so they work with every `border-radius` utility, including the side- and corner-specific ones: ```tsx
;
;
; ``` ## Shadows | CSS variable | Value | | ---------------------------- | -------------------------------------------------------------- | | `--vesper-shadow-100` | `0 0.125rem 0.375rem 0.0625rem rgba(9, 34, 33, 0.1)` | | `--vesper-shadow-200` | `0 0.25rem 0.75rem 0 rgba(9, 34, 33, 0.1)` | | `--vesper-shadow-300` | `0 0.75rem 2rem 0 rgba(9, 34, 33, 0.15)` | | `--vesper-shadow-400` | `0 1.5rem 4rem 0 rgba(9, 34, 33, 0.2)` | | `--vesper-shadow-inset-100` | `0 0.125rem 0.375rem 0 var(--vesper-alpha-contrast-100) inset` | | `--vesper-shadow-inset-200` | `0 0.125rem 0.75rem 0 var(--vesper-tint-contrast-200) inset` | | `--vesper-shadow-focus-ring` | `0 0 0 0.1875rem var(--vesper-border-focus)` | ### Tailwind shadow classes Shadow tokens are registered in Tailwind's `--shadow-*` namespace, so they are available as `box-shadow` utilities. Note that the inset tokens already include the `inset` keyword in their value, so they are also applied with `shadow-*` (not `inset-shadow-*`): ```tsx
;
; ; } ``` > [!IMPORTANT] > > Keep the content inside of your button short (1-3 words) and descriptive of the action being taken. It is not advisable to use a `Button` to render non-text content, or text content that overflows/wraps. ## Options | Prop | Type | Description | Default | | ----------- | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ----------- | | `size` | `"xs" \| "sm" \| "md" \| "lg"` | The size of the button. Affects padding and font size. | `"md"` | | `variant` | `"contrast" \| "danger" \| "ghost" \| "primary" \| "subtle" \| "tertiary" \| "warning"` | The visual style variant of the button. | `"primary"` | | `disabled` | `boolean` | When `true`, renders the button in a disabled state and prevents interaction. | `false` | | `iconLeft` | `ReactNode` | An optional icon element rendered to the left of the button content. | — | | `iconRight` | `ReactNode` | An optional icon element rendered to the right of the button content. | — | | `as` | `ElementType` | The root element type for polymorphic rendering. | `"button"` | | `children` | `ReactNode` | The content of the button. | — | All other props are forwarded to the underlying `; } ``` ### Variants A `Button` can be rendered in several different variants: | Variant | Preview | When to use | | ------------ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `"primary"` | | Default variant. Use for the main action a user is expected to take in a given context. | | `"subtle"` | | Use for secondary actions that are important but should have less visual prominence than the primary action. | | `"tertiary"` | | Use for secondary or supporting actions that need a clear boundary from surrounding content. | | `"contrast"` | | Use when an action needs strong visual contrast against its surrounding content. | | `"ghost"` | | Use for low-emphasis actions where a button's presence should be unobtrusive. | | `"danger"` | | Use for destructive or potentially irreversible actions that could result in negative consequence. | | `"warning"` | | Use for actions that may have meaningful consequences or require caution, but are not inherently destructive. | ```tsx import { Button } from "@tenstorrent/vesper/button"; export default function ButtonVariants() { return ( <> ); } ``` ### Different sizes A `Button` can be rendered in one of four sizes; `"xs"`, `"sm"`, `"md"`, or `"lg"`. The default size is `"md"`. ```tsx demo import { Button } from "@tenstorrent/vesper/button"; export default function ButtonSizes() { return (
); } ``` ### Rendering icons A `Button` can render icons to the left or the right of its content via the `iconLeft` and `iconRight` props. Simply pass your icon as a `ReactNode` to either of these props: ```tsx demo import { Button } from "@tenstorrent/vesper/button"; import { ArrowRight, Download } from "@tenstorrent/vesper/icons"; export default function ButtonsWithIcons() { return (
); } ``` ### Disabling a button You can disable a `Button` by passing `disabled={true}` or just `disabled` as a prop. Disabling a button prevents interaction and changes the color scheme: ```tsx demo import { Button } from "@tenstorrent/vesper/button"; export default function DisabledButton() { return ; } ``` ### Polymorphic usage You can use the `as` prop to specify what underlying element to render. In this case, we are passing `as="a"` to render the `Button` as an anchor link. This is helpful for apps where clicking on a button may navigate to a different page, for example: ```tsx demo import { Button } from "@tenstorrent/vesper/button"; export default function ButtonAsLink() { return ( ); } ``` You can also use other components as the underlying `as` element. For example, Next.js comes with a bespoke `Link` component that enables prefetching and client-side navigation between routes, which you may want to use instead of the HTML `` element: ```tsx import Link from "next/link"; import { Button } from "@tenstorrent/vesper/button"; export default function ButtonAsNextLink() { return ( ); } ``` --- Source: https://vesper.tenstorrent.com/components/checkbox.md # Checkbox A form checkbox input with text, supporting checked, unchecked, and indeterminate states. Built on a native `` for full form compatibility and accessibility. ```tsx demo import { Checkbox } from "@tenstorrent/vesper/checkbox"; export default function CheckboxDemo() { return ; } ``` ## Options | Prop | Type | Description | Default | | ---------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------- | | `text` | `string` | The text displayed next to the checkbox, also used as the input's default `aria-label`. **Required.** | — | | `size` | `"sm" \| "md"` | The size of the checkbox and its text. | `"md"` | | `indeterminate` | `boolean` | When `true`, renders the checkbox in an indeterminate (mixed) state with a dash icon. | `false` | | `checked` | `boolean` | The checked state of the checkbox (controlled mode). | — | | `defaultChecked` | `boolean` | The initial checked state (uncontrolled mode). | `false` | | `onChange` | `ChangeEventHandler` | Callback fired when the checked state changes. | — | | `name` | `string` | The form field name submitted with form data. | — | | `value` | `string` | The value submitted with form data when the checkbox is checked. | `"on"` | | `form` | `string` | Associates the checkbox with a `
` element by its `id`. | — | | `id` | `string` | An identifier applied to the underlying ``, used to reference it from form validation errors. | — | | `disabled` | `boolean` | When `true`, prevents interaction. | `false` | | `required` | `boolean` | When `true`, marks the input as required and appends an asterisk to the `text`. | `false` | | `ref` | `Ref` | A ref forwarded to the underlying `` element. | — | Form and accessibility props such as `autoFocus`, `tabIndex`, `aria-label`, `aria-labelledby`, `aria-describedby`, `aria-invalid`, and event handlers like `onFocus`, `onBlur`, and `onKeyDown` are forwarded to the underlying `` element, as is `ref`. Any other props, including `className`, are forwarded to the wrapping `
`, so you can either pass it an `aria-label`, or point `aria-labelledby` at the heading or label that already names the group. ## Options | Prop | Type | Description | Default | | --------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ---------- | | `name` | `string` | The name attribute shared by all inputs in the group, used for form submission. | — | | `options` | `ChoiceboxItem[]` | The list of options to render. Each has `value`, `label`, optional `description`, `disabled`, and `id`. | — | | `disabled` | `boolean` | When `true`, disables all options in the group. | `false` | | `multiselect` | `boolean` | When `true`, enables multi-select (checkbox) mode. Otherwise single-select (radio). | `false` | | `value` | `string` | The currently selected value (single-select, controlled). | — | | `defaultValue` | `string` | The initially selected value (single-select, uncontrolled). | — | | `values` | `string[]` | The currently selected values (multi-select, controlled). | — | | `defaultValues` | `string[]` | The initially selected values (multi-select, uncontrolled). | — | | `onChange` | `(value: string) => void \| (values: string[]) => void` | Callback fired when the selection changes. | — | | `required` | `boolean` | When `true`, a selection is required (single-select only). | `false` | | `min` | `number` | Minimum selections required (multi-select only). | `0` | | `max` | `number` | Maximum selections allowed (multi-select only). | `Infinity` | All other props are forwarded to the wrapping `
` element. ### `ChoiceboxItem` options | Property | Type | Description | | ------------- | --------- | ---------------------------------------------------------------------------------------- | | `value` | `string` | The value submitted with form data and passed to `onChange`. Must be unique. | | `label` | `string` | The text displayed for this option. | | `description` | `string` | An optional secondary line of text displayed below the label. | | `disabled` | `boolean` | When `true`, prevents this option from being selected. Defaults to `false`. | | `id` | `string` | An `id` applied to this option's ``, used to reference it from validation errors. | Use a `Choicebox` when each option benefits from a description and a large, card-style target. For a plain list of mutually exclusive options, reach for [the `RadioGroup` component](https://vesper.tenstorrent.com/components/radio-group.md), and for a single boolean choice, reach for [the `Checkbox` component](https://vesper.tenstorrent.com/components/checkbox.md). ## Examples ### Defining options Render a `Choicebox` by passing an array of `ChoiceboxItem` objects as the `options` prop. Each item describes a single card. `value` is what gets submitted and reported to `onChange`, `label` is the text displayed on the card, and the optional `description` adds a secondary line below it: ```tsx demo import { Choicebox } from "@tenstorrent/vesper/choicebox"; export default function ChoiceboxOptions() { return ( ); } ``` Options without a `description` render as a more compact card, as seen with the `"Asia Pacific"` option above. ### Single-select By default, a `Choicebox` operates in single-select mode, rendering each option as a radio input so that picking one deselects the others. Pass `defaultValue` to select an option initially: ```tsx demo import { Choicebox } from "@tenstorrent/vesper/choicebox"; export default function UncontrolledChoicebox() { return ( ); } ``` To control the selection, pass `value` alongside `onChange`, which receives the newly selected value: ```tsx demo import { useState } from "react"; import { Choicebox } from "@tenstorrent/vesper/choicebox"; import { Typography } from "@tenstorrent/vesper/typography"; export default function ControlledChoicebox() { const [value, setValue] = useState("monthly"); return (
Selected value: {value}
); } ``` ### Multi-select Passing `multiselect` renders each option as a checkbox, allowing any number of them to be selected. In this mode the uncontrolled prop is `defaultValues`, and `onChange` receives the full array of selected values: ```tsx demo import { Choicebox } from "@tenstorrent/vesper/choicebox"; export default function MultiSelectChoicebox() { return ( ); } ``` Controlled multi-select works the same way as single-select, except that you pass `values` instead of `value`: ```tsx demo import { useState } from "react"; import { Choicebox } from "@tenstorrent/vesper/choicebox"; import { Typography } from "@tenstorrent/vesper/typography"; export default function ControlledMultiSelectChoicebox() { const [values, setValues] = useState(["analytics"]); return (
Selected values: {values.join(", ") || "none"}
); } ``` ### Disabling options Pass `disabled` to an individual option to prevent it from being selected while leaving the rest of the group interactive. You can also pass `disabled` to the `Choicebox` component itself to disable every option at once: ```tsx demo import { Choicebox } from "@tenstorrent/vesper/choicebox"; export default function DisabledChoiceboxOptions() { return (
); } ``` ### Usage in forms `Choicebox` renders native inputs inside a `
`, so it works with regular form submission and native validation. Every selected option submits its `value` under the group's `name`, which means a multi-select group can submit several values at once: ```tsx demo import { useState } from "react"; import { Button } from "@tenstorrent/vesper/button"; import { Choicebox } from "@tenstorrent/vesper/choicebox"; import { Typography } from "@tenstorrent/vesper/typography"; export default function FormChoiceboxDemo() { const [submitted, setSubmitted] = useState(null); return (
{ event.preventDefault(); const data = new FormData(event.currentTarget); setSubmitted(data.getAll("addons").join(", ") || "none"); }} > {submitted !== null && ( Submitted values: addons={submitted} )} ); } ``` The `min` and `max` props set a custom validation message on the group, so the form above refuses to submit until between one and two add-ons are selected. In single-select mode, pass `required` instead to require that one of the options is picked: ```tsx demo import { Choicebox } from "@tenstorrent/vesper/choicebox"; export default function RequiredChoicebox() { return ( ); } ``` --- Source: https://vesper.tenstorrent.com/components/code.md # Code An inline code component for displaying short code snippets or technical terms within running text. Renders as a styled `` element with monospace typography. ```tsx demo import { Code } from "@tenstorrent/vesper/code"; import { Typography } from "@tenstorrent/vesper/typography"; export default function CodeDemo() { return ( Use the Code component to highlight inline code. ); } ``` ## Options | Prop | Type | Description | Default | | ---------- | ------------------------- | ----------------------------------- | ----------- | | `variant` | `"default" \| "contrast"` | The visual style variant to render. | `"default"` | | `children` | `ReactNode` | The inline code content. | — | Any additional props are forwarded to the underlying `` element. ## Examples ### Basic usage Use `` as you would use the `` element by wrapping inline code strings: ```tsx demo import { Code } from "@tenstorrent/vesper/code"; import { Typography } from "@tenstorrent/vesper/typography"; export default function BasicCode() { return ( Run yarn install to get started. ); } ``` ### Variants `Code` can be rendered in one of two variants: `"default"` or `"contrast"`. Use the `"contrast"` variant to render inline `Code` with increased contrast: ```tsx demo import { Code } from "@tenstorrent/vesper/code"; import { Typography } from "@tenstorrent/vesper/typography"; export default function CodeVariants() { return (
Run yarn install to get started. Run yarn install to get started.
); } ``` --- Source: https://vesper.tenstorrent.com/components/code-block.md # Code Block A syntax-highlighted code block component powered by Shiki. Supports static code strings and streaming content, line numbers, and copy-to-clipboard. ```tsx demo import typescript from "@shikijs/langs/typescript"; import { CodeBlock } from "@tenstorrent/vesper/code-block"; export default function CodeBlockDemo() { return ( {'const greeting = "Hello, world!";\nconsole.log(greeting);'} ); } ``` > [!NOTE] > > The code samples on this page are rendered using the `CodeBlock` component itself! ## Options | Prop | Type | Description | Default | | ----------------- | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | `children` | `string \| () => ReadableStream \| Promise>` | The code to render. Can be a string or a factory function returning a `ReadableStream` for streaming content. | `""` | | `lang` | `LanguageRegistration[] \| "text" \| "ansi"` | The language syntax for highlighting. Import grammar objects from `@shikijs/langs`. Use `"text"` for plain text or `"ansi"` for ANSI escape codes. | `"text"` | | `showLineNumbers` | `boolean` | Whether to show line numbers on the left-hand side. | `false` | | `copyOnHover` | `boolean` | When `true`, hides the copy-to-clipboard button until the code block is hovered. | `false` | All other props are forwarded to the underlying `
` element. To render a short snippet of code inline with other text, reach for [the `Code` component](https://vesper.tenstorrent.com/components/code.md), and for a single copyable command, [the `Snippet` component](https://vesper.tenstorrent.com/components/snippet.md). ## Examples ### Basic usage Render a `CodeBlock` by passing it plain text. Without a `lang`, the code is rendered as-is with no syntax highlighting. Every code block renders a copy-to-clipboard button in its top-right corner, which copies the code exactly as it was passed in: ```tsx demo import { CodeBlock } from "@tenstorrent/vesper/code-block"; export default function BasicCodeBlock() { return Hello, world!; } ``` ### Syntax highlighting `CodeBlock` ships without any built-in grammars, and expects you to provide them yourself. Individual grammars can be imported from `@shikijs/langs/*` and supplied to the component. For example, to achieve `javascript` syntax highlighting you would import the grammar and pass it to the component via the `lang` prop: ```tsx demo import javascript from "@shikijs/langs/javascript"; import { CodeBlock } from "@tenstorrent/vesper/code-block"; export default function HighlightedCodeBlock() { return ( {"const count = 42;\nconsole.log(count);"} ); } ``` > [!NOTE] > > Importing grammars individually keeps your bundle to only the languages you actually render. #### Plain text and ANSI Plain text and ANSI come prebundled and don't require supplying any grammar objects. You can simply set the `lang` prop to `"text"` or `"ansi"`: ```tsx demo import { CodeBlock } from "@tenstorrent/vesper/code-block"; const BUILD_LOGS = [ "\u001b[1;36m● vesper\u001b[0m \u001b[2mv0.2.0\u001b[0m", "\u001b[32m✓\u001b[0m compiled 42 modules", "\u001b[33m!\u001b[0m 1 warning found", "\u001b[31m✗\u001b[0m failed to resolve \u001b[1m./missing-module\u001b[0m", ].join("\n"); export default function AnsiCodeBlock() { return {BUILD_LOGS}; } ``` This is what you want for output captured from a terminal, such as build logs or test runs, where the color information is already encoded in the text itself. #### Custom grammars You can provide your own custom grammars by passing them into the `lang` prop as an array of TextMate grammar objects: ```tsx import { CodeBlock } from "@tenstorrent/vesper/code-block"; import customGrammar from "./custom-grammar.json"; export default function CustomGrammarCodeBlock({ code }: { code: string }) { return {code}; } ``` [tm-grammars package on GitHub](https://github.com/shikijs/textmate-grammars-themes/tree/main/packages/tm-grammars) ### With line numbers Passing `showLineNumbers` renders a line number gutter down the left-hand side of the code block: ```tsx demo import python from "@shikijs/langs/python"; import { CodeBlock } from "@tenstorrent/vesper/code-block"; const CODE = `def greet(name: str) -> str: message = f"Hello, {name}!" print(message) return message greet("world")`; export default function NumberedCodeBlock() { return ( {CODE} ); } ``` Line numbers are rendered decoratively, so they are never included in the text copied to the clipboard. ### Streaming content To stream code as it arrives (build logs, LLM output, etc.), pass a factory function that returns a `ReadableStream` instead of a string. Tokens are highlighted as they arrive, and the block scrolls to follow the output unless the user scrolls away from the bottom: ```tsx demo import { useState } from "react"; import typescript from "@shikijs/langs/typescript"; import { Button } from "@tenstorrent/vesper/button"; import { CodeBlock } from "@tenstorrent/vesper/code-block"; const CODE = `async function getUser(id: string) { const response = await fetch(\`/api/users/\${id}\`); if (!response.ok) throw new Error("Request failed"); return response.json(); }`; /** * A factory is used instead of a raw stream because a stream can only be read * once: this way the component can create a fresh one whenever it needs to */ function createCodeStream() { let remaining = CODE; return new ReadableStream({ async start(controller) { while (remaining.length) { controller.enqueue(remaining.slice(0, 8)); remaining = remaining.slice(8); await new Promise((resolve) => setTimeout(resolve, 60)); } controller.close(); }, }); } export default function StreamedCodeBlock() { const [run, setRun] = useState(0); return (
{createCodeStream}
); } ``` ### Showing clipboard button on hover The copy-to-clipboard button is always visible by default; pass `copyOnHover` to hide it until the code block is hovered: ```tsx demo import json from "@shikijs/langs/json"; import { CodeBlock } from "@tenstorrent/vesper/code-block"; const CODE = `{ "name": "@tenstorrent/vesper", "version": "0.2.0" }`; export default function HoverCopyCodeBlock() { return ( {CODE} ); } ``` --- Source: https://vesper.tenstorrent.com/components/combobox.md # Combobox A form-ready, searchable select component that filters a list of options as the user types. It supports both controlled and uncontrolled usage. ```tsx demo import { Combobox } from "@tenstorrent/vesper/combobox"; const FRUITS = [ { label: "Apple", value: "apple" }, { label: "Banana", value: "banana" }, { label: "Cherry", value: "cherry" }, { label: "Grape", value: "grape" }, { label: "Mango", value: "mango" }, { label: "Orange", value: "orange" }, { label: "Peach", value: "peach" }, { label: "Pear", value: "pear" }, { label: "Pineapple", value: "pineapple" }, { label: "Strawberry", value: "strawberry" }, ]; export default function ComboboxDemo() { return ( ); } ``` > [!IMPORTANT] > > Always give a `Combobox` an accessible name by either [pairing it with a `
` element. ### `ComboboxItem` options | Property | Type | Description | | -------- | -------- | ------------------------------------------------------------------------------------------------- | | `label` | `string` | The text displayed for this option in the dropdown, and used to filter options as the user types. | | `value` | `string` | The underlying value submitted with form data and passed to `onValueChange`. Must be unique. | ## Examples ### Basic usage Render a `Combobox` by supplying an array of `options`. Each `option` can either be a `ComboboxItem`, or a plain string. Use `ComboboxItem` objects when the value associated with each item differs from the text you want to display: ```tsx demo import { Combobox } from "@tenstorrent/vesper/combobox"; export default function ObjectOptionsCombobox() { return ( ); } ``` When the displayed text is the same as the value of the item, a string option is a convenient shorthand: ```tsx demo import { Combobox } from "@tenstorrent/vesper/combobox"; export default function StringOptionsCombobox() { return ( ); } ``` Both shapes can be mixed in the same `options` array. Options are filtered against their `label` as the user types, and selecting an option fills the input with that option's `label` while reporting its `value`. ### Uncontrolled vs controlled Render a `Combobox` in an uncontrolled fashion to let it keep track of its own selection. Pass `defaultValue` if you need an option to be selected initially: ```tsx demo import { Combobox } from "@tenstorrent/vesper/combobox"; export default function UncontrolledCombobox() { return ( ); } ``` If you need to control which option is selected, use the `value` and `onValueChange` props. `value` is the `value` of the selected option, and is `null` when nothing is selected. Clearing the selection with the clear button calls `onValueChange` with `null`: ```tsx demo import { useState } from "react"; import { Button } from "@tenstorrent/vesper/button"; import { Combobox } from "@tenstorrent/vesper/combobox"; import { Typography } from "@tenstorrent/vesper/typography"; const FRUITS = [ { label: "Apple", value: "apple" }, { label: "Banana", value: "banana" }, { label: "Cherry", value: "cherry" }, ]; export default function ControlledCombobox() { const [fruit, setFruit] = useState("apple"); return (
Selected value: {fruit ?? "null"}
); } ``` ### Controlling the input's text The text typed into the input is tracked separately from the selected value. Use `defaultInputValue` to seed the input's text, or `inputValue` and `onInputValueChange` to control it: ```tsx demo import { useState } from "react"; import { Combobox } from "@tenstorrent/vesper/combobox"; import { Typography } from "@tenstorrent/vesper/typography"; export default function ControlledInputValueCombobox() { const [query, setQuery] = useState(""); return (
Input value: {query || "empty"}
); } ``` Controlling the input's text is most useful when the options themselves depend on what the user typed, eg. when they are fetched from a server: ```tsx import { useEffect, useState } from "react"; import { Combobox } from "@tenstorrent/vesper/combobox"; export default function AsyncCombobox() { const [query, setQuery] = useState(""); const [options, setOptions] = useState([]); useEffect(() => { const controller = new AbortController(); fetch(`/api/repositories?q=${encodeURIComponent(query)}`, { signal: controller.signal, }) .then((response) => response.json()) .then((repositories: string[]) => setOptions(repositories)) .catch(() => {}); return () => controller.abort(); }, [query]); return ( ); } ``` > [!NOTE] > > Options are always filtered against the text in the input. When you supply options that have already been filtered by a server, make sure the results have labels that match against what the user typed. ### Different sizes The `Combobox` component can be rendered at `sm`, `md`, or `lg` size, defaulting to `md`. Size affects the height, padding, and text styles of the input, as well as the size of its icons. ```tsx demo import { Combobox } from "@tenstorrent/vesper/combobox"; const COUNTRIES = ["Canada", "Japan", "Norway"]; export default function ComboboxSizes() { return (
); } ``` ### Variants The `Combobox` component can be rendered in one of four variants: `default`, `warning`, `success`, or `error`. The variant determines the colour scheme of the input. #### Default variant A `Combobox` renders with the `default` variant when no `variant` prop is specified. ```tsx demo import { Combobox } from "@tenstorrent/vesper/combobox"; export default function DefaultCombobox() { return ( ); } ``` #### Warning variant Use the `warning` variant to flag a selection that needs attention, but that doesn't prevent the form from being submitted: ```tsx demo import { Combobox } from "@tenstorrent/vesper/combobox"; export default function WarningCombobox() { return ( ); } ``` #### Success variant The `success` variant is best suited for confirming that a selection has been validated as expected: ```tsx demo import { Combobox } from "@tenstorrent/vesper/combobox"; export default function SuccessCombobox() { return ( ); } ``` #### Error variant The `error` variant highlights a missing or invalid selection that must be corrected before the form can be submitted: ```tsx demo import { Combobox } from "@tenstorrent/vesper/combobox"; export default function ErrorCombobox() { return ( ); } ``` ### Customising the empty state When no option matches the text in the input, the dropdown renders a short empty state message. Use `emptyStateText` to tailor it to the data being searched: ```tsx demo import { Combobox } from "@tenstorrent/vesper/combobox"; export default function ComboboxWithCustomEmptyState() { return ( ); } ``` ### Disabled and read-only Pass `disabled` to prevent all interaction with the input and its dropdown: ```tsx demo import { Combobox } from "@tenstorrent/vesper/combobox"; export default function DisabledCombobox() { return ( ); } ``` Pass `readOnly` when the value should be visible and submitted with the form, but not editable: ```tsx demo import { Combobox } from "@tenstorrent/vesper/combobox"; export default function ReadOnlyCombobox() { return ( ); } ``` ### Controlling the dropdown The dropdown opens when the input or the caret is clicked, and as the user types. You can open it initially with `defaultOpen`, or control it entirely with `open` and `onOpenChange`: ```tsx demo import { useState } from "react"; import { Button } from "@tenstorrent/vesper/button"; import { Combobox } from "@tenstorrent/vesper/combobox"; export default function ControlledDropdownCombobox() { const [open, setOpen] = useState(false); return (
); } ``` ### Usage in forms `Combobox` renders a form control, so passing a `name` includes the selected value in form submissions, and `required` participates in native form validation: ```tsx demo import { useState } from "react"; import { Button } from "@tenstorrent/vesper/button"; import { Combobox } from "@tenstorrent/vesper/combobox"; import { Typography } from "@tenstorrent/vesper/typography"; const FRUITS = [ { label: "Apple", value: "apple" }, { label: "Banana", value: "banana" }, { label: "Cherry", value: "cherry" }, ]; export default function FormComboboxDemo() { const [submitted, setSubmitted] = useState(null); return ( { event.preventDefault(); const data = new FormData(event.currentTarget); setSubmitted(String(data.get("fruit") ?? "")); }} > {submitted !== null && ( Submitted value: {submitted} )} ); } ``` The value submitted is the `value` of the selected option, not the text displayed in the input. The form above submits `fruit=apple` when "Apple" is selected. Use the `form` prop to associate the field with a `
` rendered elsewhere on the page: ```tsx import { Button } from "@tenstorrent/vesper/button"; import { Combobox } from "@tenstorrent/vesper/combobox"; export default function ComboboxFormPropDemo() { return (
{/* rendered outside of the form, but submitted with it */}
); } ``` ### Accessing the underlying element Use the `ref` prop when you need direct access to the underlying `` element, eg. to focus it when a page loads: ```tsx import { useEffect, useRef } from "react"; import { Combobox } from "@tenstorrent/vesper/combobox"; export default function ComboboxRefDemo() { const ref = useRef(null); useEffect(() => { ref.current?.focus(); }, []); return ( ); } ``` --- Source: https://vesper.tenstorrent.com/components/form-input-message.md # Form Input Message A message displayed alongside a form input, paired with a variant-specific icon. It is rendered as a native `` element, which is an implicit live region (`role="status"`), so a message that appears or changes is announced by screen readers. ```tsx demo import { FormInputMessage } from "@tenstorrent/vesper/form-input-message"; export default function FormInputMessageDemo() { return ; } ``` This is the same component used to render the `message` of [Text Input](https://vesper.tenstorrent.com/components/text-input.md) and [TextArea](https://vesper.tenstorrent.com/components/text-area.md), so you'll usually only reach for it directly when describing a form control that Vesper doesn't provide. ## Options | Prop | Type | Description | Default | | --------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | ----------- | | `variant` | `"default" \| "warning" \| "success" \| "error"` | The visual variant, which determines the colour scheme and message icon. | `"default"` | | `message` | `string` | The message text to display. When omitted or empty, no content is rendered, but the `` element stays in the document. | — | You may also pass any additional props to the underlying `` element. Most commonly, this will be an `id` so the message can be referenced by an input's `aria-describedby`. Messages preserve line breaks (`white-space: pre-line`), so a `message` containing `\n` renders across multiple lines. ## Examples ### Basic usage Pass a `message` to render it alongside the icon for the current variant: ```tsx demo import { FormInputMessage } from "@tenstorrent/vesper/form-input-message"; export default function BasicFormInputMessage() { return ( ); } ``` ### Variants The `FormInputMessage` component can be rendered in one of four variants: `default`, `warning`, `success`, or `error`. The variant determines the colour scheme of the message, as well as the icon it is paired with. #### Default variant A `FormInputMessage` renders with the `default` variant when no `variant` prop is specified. It is best suited for neutral hints, such as formatting requirements: ```tsx demo import { FormInputMessage } from "@tenstorrent/vesper/form-input-message"; export default function DefaultFormInputMessage() { return ( ); } ``` #### Warning variant Use the `warning` variant to flag a value that needs attention, but that doesn't prevent the form from being submitted: ```tsx demo import { FormInputMessage } from "@tenstorrent/vesper/form-input-message"; export default function WarningFormInputMessage() { return ( ); } ``` #### Success variant The `success` variant is best suited for confirming that a value has been validated as expected: ```tsx demo import { FormInputMessage } from "@tenstorrent/vesper/form-input-message"; export default function SuccessFormInputMessage() { return ( ); } ``` #### Error variant The `error` variant highlights an invalid value that must be corrected before the form can be submitted: ```tsx demo import { FormInputMessage } from "@tenstorrent/vesper/form-input-message"; export default function ErrorFormInputMessage() { return ( ); } ``` ### Empty and undefined messages `FormInputMessage` **always renders its `` element**, even when `message` is `undefined` or an empty string. In that case the icon and text are not rendered, and the element is visually hidden — it takes up no space in the layout and shows nothing on screen, but it remains in the DOM: ```tsx demo import { FormInputMessage } from "@tenstorrent/vesper/form-input-message"; export default function EmptyFormInputMessages() { // each of these still renders a visually-hidden element return ( <> ); } ``` This is deliberate, and is what makes validation messages announce reliably. An `` is an implicit live region, and a live region must already exist in the DOM **before** its content changes for assistive technology to announce that change. If the element were mounted at the same moment its message appeared, screen readers would typically miss the update entirely. > [!WARNING] > > Don't conditionally render `FormInputMessage`. Keep it mounted at all times so the live region is always in place when the `message` changes. ```tsx import { FormInputMessage } from "@tenstorrent/vesper/form-input-message"; export default function ValidationMessage({ error }: { error?: string }) { return ( <> {/* 👍 the output is mounted up front, so `error` is announced when it is set */} {/* 👎 the output is mounted alongside its content, so it may not be announced */} {error && } ); } ``` The element exposes a `data-message` attribute (`"true"` or `"false"`) reflecting whether it currently has a message, which is useful if you need to style or query the empty state. ### Describing an input To associate a message with an input, give the `FormInputMessage` an `id` and reference it from the input's `aria-describedby`: ```tsx import { FormInputMessage } from "@tenstorrent/vesper/form-input-message"; export default function ApiKeyField() { return (
); } ``` --- Source: https://vesper.tenstorrent.com/components/icon-button.md # Icon Button A button component that renders a single icon without text. Built on top of `Button`, it inherits all size and variant options while enforcing an icon-only layout. ```tsx demo import { IconButton } from "@tenstorrent/vesper/icon-button"; import { Gear } from "@tenstorrent/vesper/icons"; export default function IconButtonDemo() { return } aria-label="Settings" />; } ``` > [!IMPORTANT] > > Always give an `IconButton` an accessible name by giving it an `aria-label`. Without one, assistive technology announces the button with no indication of what it is for. ## Options | Prop | Type | Description | Default | | ---------- | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ----------- | | `icon` | `ReactNode` | The icon element to be rendered inside the button. | — | | `size` | `"xs" \| "sm" \| "md" \| "lg"` | The size of the button. Affects padding and icon size. | `"md"` | | `variant` | `"contrast" \| "danger" \| "ghost" \| "primary" \| "subtle" \| "tertiary" \| "warning"` | The visual style variant of the button. | `"primary"` | | `disabled` | `boolean` | When `true`, renders the button in a disabled state and prevents interaction. | `false` | | `as` | `ElementType` | The root element type for polymorphic rendering. | `"button"` | All other props are forwarded to the underlying `
); } ``` > [!IMPORTANT] > > A `Menu`'s children must be a single React element that can receive a ref and event handlers, since that element becomes the menu's trigger. Passing a fragment, plain text, or multiple elements renders the children as-is, without a menu attached to them. ## Options | Prop | Type | Description | Default | | -------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------- | | `items` | `MenuItemProps[]` | The list of menu items rendered in the dropdown, in the order they are provided. | — | | `children` | `ReactElement` | The trigger element that opens the menu. | — | | `width` | `number` | The width of the menu dropdown in pixels. Scales with the base rem size. | `200` | | `side` | `"top" \| "bottom" \| "left" \| "right"` | The preferred side of the trigger to render the menu against. | `"bottom"` | | `sideOffset` | `number` | The distance in pixels from the trigger to the menu. Scales with the base rem size. | `8` | | `align` | `"start" \| "center" \| "end"` | The alignment of the menu relative to the trigger along the perpendicular axis. | `"start"` | | `alignOffset` | `number` | An offset in pixels from the aligned edge of the trigger. Scales with the base rem size. | `0` | | `open` | `boolean` | Controls the open state of the menu (controlled mode). | — | | `defaultOpen` | `boolean` | Whether the menu is open by default (uncontrolled mode). | `false` | | `onOpenChange` | `(open: boolean) => void` | Callback fired when the open state changes. | — | | `container` | `HTMLElement \| ShadowRoot \| null \| RefObject` | Specify the element or shadow root to portal the menu into. | — | | `anchor` | `HTMLElement \| null \| RefObject` | Specify the element to position the menu against. Default behavior anchors the menu to the trigger element. | — | ### `MenuItemProps` options | Property | Type | Description | Default | | ------------- | --------------------------------------------------------------- | ----------------------------------------------------------------- | ----------- | | `text` | `string` | The text label displayed for the menu item. | — | | `description` | `string` | An optional secondary description displayed below the text label. | — | | `icon` | `ReactNode` | An optional icon element rendered to the left of the text label. | — | | `style` | `"default" \| "danger" \| "locked" \| "selected" \| "disabled"` | The visual and behavioral style of the menu item. | `"default"` | | `onSelect` | `() => void` | Callback fired when the menu item is selected. **Required.** | — | ## Examples ### Basic usage Render a `Menu` by passing it an array of `items` and a single element to use as its trigger. Every item needs `text` to display and an `onSelect` callback to run when it is chosen: ```tsx demo import { useState } from "react"; import { Button } from "@tenstorrent/vesper/button"; import { Menu } from "@tenstorrent/vesper/menu"; import { Typography } from "@tenstorrent/vesper/typography"; export default function BasicMenu() { const [selected, setSelected] = useState(null); return (
setSelected("Rename") }, { text: "Duplicate", onSelect: () => setSelected("Duplicate") }, { text: "Delete", style: "danger", onSelect: () => setSelected("Delete"), }, ]} > Last selected item: {selected ?? "none"}
); } ``` Clicking the trigger opens the menu, and choosing an item fires its `onSelect` callback function and closes the menu. Any element that can be focused and receive a ref works as a trigger. ### Describing items Each item can render an `icon` to the left of its label, and a `description` underneath it for the items whose outcome is not obvious from their label alone. Descriptions are optional, and can be mixed with items that only have a label: ```tsx demo import { IconButton } from "@tenstorrent/vesper/icon-button"; import { Copy, Download, Ellipses, Gear } from "@tenstorrent/vesper/icons"; import { Menu } from "@tenstorrent/vesper/menu"; export default function DescriptiveMenu() { return ( , description: "Anyone with the link can view", onSelect: () => {}, }, { text: "Download", icon: , description: "Exports the current view as a CSV", onSelect: () => {}, }, { text: "Settings", icon: , onSelect: () => {}, }, ]} > } /> ); } ``` ### Item styles Each item can be rendered in one of five styles via its `style` prop: | Style | When to use | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | | `"default"` | Default style. Use for regular actions. | | `"selected"` | Use when the item represents the currently active option out of a set. Renders a checkmark at the end of the item. | | `"danger"` | Use for destructive or potentially irreversible actions. Renders the item in the error color scheme. | | `"locked"` | Use for actions the user could take, but currently cannot, eg. because of their permissions. Renders a lock icon and prevents interaction. | | `"disabled"` | Use for actions that do not apply in the current context, eg. pasting with an empty clipboard. Dims the item and prevents interaction. | ```tsx demo import { Button } from "@tenstorrent/vesper/button"; import { Blackhole, Globe, Tenstorrent } from "@tenstorrent/vesper/icons"; import { Menu } from "@tenstorrent/vesper/menu"; export default function MenuItemStyles() { return ( , onSelect: () => {} }, { text: "Selected item", icon: , style: "selected", onSelect: () => {}, }, { text: "Danger item", icon: , style: "danger", onSelect: () => {}, }, { text: "Locked item", style: "locked", onSelect: () => {} }, { text: "Disabled item", style: "disabled", onSelect: () => {} }, ]} > ); } ``` > [!NOTE] > > `"locked"` and `"disabled"` items cannot be activated: their `onSelect` never fires, and the menu stays open when one of them is clicked. They remain reachable with the keyboard, so assistive technology can still announce them and their state. ### Setting the width A menu is `200px` wide by default. Pass a `number` to the `width` prop to render it with a different width. The value passed scales with the base rem size: ```tsx demo import { Button } from "@tenstorrent/vesper/button"; import { Menu } from "@tenstorrent/vesper/menu"; const ITEMS = [ { text: "Rename", onSelect: () => {} }, { text: "Duplicate", onSelect: () => {} }, ]; export default function MenuWidths() { return ( ); } ``` ### Positioning the menu By default, a `Menu` opens below its trigger and is aligned to its starting edge. You can adjust this via the `side`, `sideOffset`, `align`, and `alignOffset` props. The `side` prop determines which trigger edge the menu sits against. Possible values are `"top"`, `"bottom"`, `"left"`, and `"right"`. The default value is `"bottom"`. The `sideOffset` prop determines how far away in pixels the menu is from its trigger's edge. The default value is `8`. This value scales with the base rem size. The `align` prop determines which end of the `side` to align the menu to. Possible values are `"start"`, `"center"`, and `"end"`. The default value is `"start"`. The `alignOffset` prop adjusts how many pixels the menu is offset from its alignment. The default value is `0`. This value scales with the base rem size. These props express a preference rather than a guarantee: a menu that would otherwise overflow the viewport is flipped and shifted to stay on screen. The demo below shows how the `side` and `align` props interact with each other: ```tsx demo import { useState } from "react"; import { Button } from "@tenstorrent/vesper/button"; import { Menu, type MenuProps } from "@tenstorrent/vesper/menu"; import { Select } from "@tenstorrent/vesper/select"; type MenuSide = NonNullable; type MenuAlign = NonNullable; const SIDES = ["top", "bottom", "left", "right"] as const; const ALIGNMENTS = ["start", "center", "end"] as const; export default function MenuPositionPlayground() { const [side, setSide] = useState("bottom"); const [align, setAlign] = useState("start"); return (
setAlign(align as MenuAlign)} options={[...ALIGNMENTS]} style={{ width: "var(--vesper-spacing-28)" }} />
{} }, { text: "Duplicate", onSelect: () => {} }, ]} >
); } ``` ### Controlling the open state By default, a `Menu` tracks its own open state. Pass `defaultOpen` when a menu should already be open on first render, while still letting it manage itself afterwards: ```tsx import { Button } from "@tenstorrent/vesper/button"; import { Menu } from "@tenstorrent/vesper/menu"; export default function DefaultOpenMenu() { return ( {} }, { text: "Duplicate", onSelect: () => {} }, ]} > ); } ``` If you need to own the open state, use the `open` and `onOpenChange` props together. `onOpenChange` fires with the menu's next open state value: ```tsx demo import { useState } from "react"; import { Button } from "@tenstorrent/vesper/button"; import { Menu } from "@tenstorrent/vesper/menu"; import { Typography } from "@tenstorrent/vesper/typography"; export default function ControlledMenu() { const [open, setOpen] = useState(false); return (
{} }, { text: "Duplicate", onSelect: () => {} }, ]} >
Open state: {String(open)}
); } ``` ### Anchoring the menu elsewhere A menu is positioned against its trigger by default. Pass the `anchor` prop an element or an element ref to position it against something else. The menu below is aligned to the end of the card rather than its trigger: ```tsx demo import { useRef } from "react"; import { IconButton } from "@tenstorrent/vesper/icon-button"; import { Ellipses } from "@tenstorrent/vesper/icons"; import { Material } from "@tenstorrent/vesper/material"; import { Menu } from "@tenstorrent/vesper/menu"; import { Typography } from "@tenstorrent/vesper/typography"; export default function AnchoredMenu() { const cardRef = useRef(null); return ( Inference cluster {} }, { text: "View logs", onSelect: () => {} }, { text: "Delete", style: "danger", onSelect: () => {} }, ]} > } /> ); } ``` ### Rendering the menu in another container A menu renders in a portal, attached to `document.body`, so it is never clipped by the overflow of the elements it sits in. Pass the `container` prop an element, a shadow root, or a ref to either when it needs to be portalled somewhere else: ```tsx import { useRef } from "react"; import { Button } from "@tenstorrent/vesper/button"; import { Menu } from "@tenstorrent/vesper/menu"; export default function PortalledMenu() { const containerRef = useRef(null); return (
{} }, { text: "Duplicate", onSelect: () => {} }, ]} >
); } ``` > [!NOTE] > > A `Menu` rendered inside a `` (which [Modal](https://vesper.tenstorrent.com/components/modal.md) and [Sheet](https://vesper.tenstorrent.com/components/sheet.md) both use) is portalled into that dialog automatically, so its items stay above the dialog's content. Passing `container` explicitly always takes precedence over this behavior. --- Source: https://vesper.tenstorrent.com/components/modal.md # Modal A dialog overlay component for displaying focused content that requires user attention or interaction. Supports a title, description, action buttons, and optional form wrapping. Controlled via an imperative ref with `open()` and `close()` methods. ```tsx demo import { Button } from "@tenstorrent/vesper/button"; import { Modal, useModal } from "@tenstorrent/vesper/modal"; export default function ModalDemo() { const modal = useModal(); return ( <> Additional content can be placed here. ); } ``` ## Options | Prop | Type | Description | Default | | --------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `title` | `string` | The title text displayed in the modal header. Used for `aria-labelledby`. **Required.** | — | | `description` | `string` | A description displayed below the title. Used for `aria-describedby`. **Required.** | — | | `width` | `number \| string` | The width of the modal container. A number is interpreted as pixels. | `452` | | `maxHeight` | `number \| string` | The maximum height of the modal container. A number is interpreted as pixels. | `640` | | `buttons` | `ButtonProps[]` | An optional array of button props for action buttons. The last button defaults to `"primary"` variant; others default to `"tertiary"`. | — | | `buttonsAlignment` | `"start" \| "end" \| "fill" \| "between"` | Controls the horizontal alignment of the action buttons. | `"end"` | | `ref` | `Ref` | A ref exposing imperative `open()` and `close()` methods. | — | | `closeOnClickOutside` | `boolean` | When `true`, clicking the backdrop closes the modal. | `false` | | `form` | `FormProps` | When provided, wraps the modal content in a `
` element with the given form attributes. | — | | `children` | `ReactNode` | The content displayed inside the modal body. | — | All other props are forwarded to the underlying `` element. A `Modal` always blocks interaction with the rest of the page while it is open. When the user should be able to keep working with the content behind the panel, reach for [the `Sheet` component](https://vesper.tenstorrent.com/components/sheet.md) in `popover` mode instead. ## Examples ### Opening and closing a modal A `Modal` renders closed, and is opened imperatively. The `useModal` hook returns everything needed for this: a `ref` to pass to the modal, plus `open()` and `close()` functions: ```tsx demo import { Button } from "@tenstorrent/vesper/button"; import { Modal, useModal } from "@tenstorrent/vesper/modal"; export default function BasicModal() { const modal = useModal(); return ( <> You can start exploring features right away. ); } ``` `Modal` renders a native ``, so an open modal traps focus, blocks interaction with the rest of the page, and closes when the user presses `Escape`. The close button in the header closes it too. ### Action buttons Pass an array of button props to `buttons` to render action buttons along the bottom of the modal. The last button defaults to the `primary` variant and the rest default to `tertiary`, which can be overridden with the `variant` prop: ```tsx demo import { Button } from "@tenstorrent/vesper/button"; import { Modal, useModal } from "@tenstorrent/vesper/modal"; export default function ModalWithButtons() { const modal = useModal(); return ( <> ); } ``` Buttons are aligned to the end of the modal by default. Use `buttonsAlignment` to lay them out differently; use `"start"` to render buttons towards the left edge of the modal, `"between"` to space buttons as far apart from each other as possible, or `"fill"` to have buttons occupy as much available space as possible: ```tsx demo import { Button } from "@tenstorrent/vesper/button"; import { Modal, useModal } from "@tenstorrent/vesper/modal"; export default function ModalButtonsAlignment() { const modal = useModal(); return ( <> ); } ``` ### Sizing A modal is `452px` wide and at most `640px` tall by default. Pass `width` and `maxHeight` to change that, either as numbers (interpreted as pixels) or as any CSS length string: ```tsx demo import { Button } from "@tenstorrent/vesper/button"; import { Modal, useModal } from "@tenstorrent/vesper/modal"; export default function WideModal() { const modal = useModal(); return ( <> Content taller than the modal scrolls within its body, keeping the header and action buttons in place. ); } ``` ### Closing on click outside Clicking the backdrop does nothing by default, which keeps users from dismissing a modal by accident. Pass `closeOnClickOutside` to allow users to dismiss a modal by clicking the backdrop: ```tsx demo import { Button } from "@tenstorrent/vesper/button"; import { Modal, useModal } from "@tenstorrent/vesper/modal"; export default function DismissibleModal() { const modal = useModal(); return ( <> This modal can be dismissed by clicking the backdrop behind it. ); } ``` ### Usage in forms Passing the `form` prop wraps the modal's contents in a `` element, so the action buttons can submit it natively with `type="submit"`, and native validation runs before the submit handler fires: ```tsx demo import { useState } from "react"; import { Button } from "@tenstorrent/vesper/button"; import { Modal, useModal } from "@tenstorrent/vesper/modal"; import { TextInput } from "@tenstorrent/vesper/text-input"; import { Typography } from "@tenstorrent/vesper/typography"; export default function FormModal() { const modal = useModal(); const [submitted, setSubmitted] = useState(null); return (
{submitted && ( Created project: {submitted} )} { event.preventDefault(); const data = new FormData(event.currentTarget); setSubmitted(String(data.get("name") ?? "")); modal.close(); }, }} buttons={[ { children: "Cancel", type: "button", onClick: modal.close }, { children: "Create", type: "submit" }, ]} >
); } ``` > [!NOTE] > > Buttons rendered inside a `form` default to `type="submit"`, so give any button that should not submit an explicit `type="button"`, as the cancel button above does. --- Source: https://vesper.tenstorrent.com/components/progress-bar.md # Progress Bar A horizontal progress indicator that visualizes completion as a percentage. Supports a smooth default mode and a stepped mode that snaps to discrete intervals. ```tsx demo import { ProgressBar } from "@tenstorrent/vesper/progress-bar"; export default function ProgressBarDemo() { return ; } ``` ## Options | Prop | Type | Description | Default | | ---------------------- | ----------------------- | --------------------------------------------------------------------------------------------- | ------------ | | `value` | `number` | The progress value from `0` to `100`. | — | | `size` | `"sm" \| "md" \| "lg"` | The rendered size of the progress bar. | `"md"` | | `variant` | `"default" \| "steps"` | `"default"` renders a smooth indicator. `"steps"` clamps the width to the nearest tick value. | `"default"` | | `steps` | `number` | The number of segments when variant is `"steps"`. Must be an integer greater than `0`. | `10` | | `stepRoundingStrategy` | `(n: number) => number` | Determines how to clamp progress to the nearest tick value. | `Math.round` | | `animated` | `boolean` | Whether to animate progress bar value changes. | `false` | All other props are forwarded to the wrapping `
` element. > [!NOTE] > > A `ProgressBar` is announced with an `aria-label` of `"Progress"` by default. Pass your own `aria-label` when a page renders more than one, so that each is announced with what it is tracking. ## Examples ### Basic usage Render a `ProgressBar` by giving it a numeric `value` prop from `0` to `100`: ```tsx demo import { ProgressBar } from "@tenstorrent/vesper/progress-bar"; export default function BasicProgressBar() { return ; } ``` ### Different sizes A `ProgressBar` can be rendered in one of three sizes, `"sm"`, `"md"`, or `"lg"`. The default size is `"md"`: ```tsx demo import { ProgressBar } from "@tenstorrent/vesper/progress-bar"; export default function ProgressBarSizes() { return (
); } ``` ### Rendering step intervals A `ProgressBar` can be rendered in one of two variants, `"default"` or `"steps"`. The default variant is `"default"`, which renders a `ProgressBar` with no step intervals. To render a `ProgressBar` with step intervals, use the `"steps"` variant: ```tsx demo import { ProgressBar } from "@tenstorrent/vesper/progress-bar"; export default function SteppedProgressBar() { return ; } ``` A `"steps"` variant `ProgressBar` will visually snap the progress indicator to the nearest interval, using `Math.round` by default. To adjust how step-rounding is calculated, you can pass a custom rounding function to the component via the `stepRoundingStrategy` prop: ```tsx demo import { ProgressBar } from "@tenstorrent/vesper/progress-bar"; export default function StepRoundingStrategies() { return (
{/* rounds with `Math.ceil` instead of `Math.round` */} {/* rounds with `Math.floor` instead of `Math.round` */}
); } ``` By default, when rendering a stepped `ProgressBar`, it will render with `10` steps. To modify the number of steps rendered, pass any positive integer value to the `steps` prop: ```tsx demo import { ProgressBar } from "@tenstorrent/vesper/progress-bar"; export default function FiveStepProgressBar() { return ; } ``` ### Animating progress To animate a `ProgressBar` as its `value` changes, you can pass `animated={true}` or just `animated` as a prop: ```tsx demo import { useState } from "react"; import { Button } from "@tenstorrent/vesper/button"; import { ProgressBar } from "@tenstorrent/vesper/progress-bar"; import { Typography } from "@tenstorrent/vesper/typography"; export default function AnimatedProgressBars() { const [value, setValue] = useState(66); return (
Current value: {value}
); } ``` --- Source: https://vesper.tenstorrent.com/components/radio-group.md # Radio Group A group of mutually exclusive radio inputs with labels. Supports horizontal and vertical layouts, controlled and uncontrolled modes, and individual option disabling. ```tsx demo import { RadioGroup } from "@tenstorrent/vesper/radio-group"; export default function RadioGroupDemo() { return ( ); } ``` ## Options | Prop | Type | Description | Default | | -------------- | ---------------------------- | ----------------------------------------------------------------------------------- | ------------ | | `name` | `string` | The name attribute shared by all radio inputs, used for form submission. | — | | `options` | `RadioGroupItem[]` | The list of radio options. Each has `value`, `label`, optional `disabled` and `id`. | — | | `size` | `"sm" \| "md"` | The size of the radio inputs and labels. | `"md"` | | `orientation` | `"horizontal" \| "vertical"` | The layout direction of the radio options. | `"vertical"` | | `value` | `string` | The currently selected value (controlled mode). | — | | `defaultValue` | `string` | The initially selected value (uncontrolled mode). | — | | `onChange` | `(value: string) => void` | Callback fired when the selected value changes. | — | | `required` | `boolean` | When `true`, a selection is required for form validation. | `false` | | `disabled` | `boolean` | When `true`, disables all options. | `false` | All other props are forwarded to the wrapping `
` element. ### `RadioGroupItem` options | Property | Type | Description | | ---------- | --------- | ---------------------------------------------------------------------------------------- | | `value` | `string` | The value submitted with form data and passed to `onChange`. Must be unique. | | `label` | `string` | The text displayed next to the radio input. | | `disabled` | `boolean` | When `true`, prevents this individual option from being selected. Defaults to `false`. | | `id` | `string` | An `id` applied to this option's ``, used to reference it from validation errors. | Use a `RadioGroup` when exactly one option out of many must be chosen. Reach for [the `Choicebox` component](https://vesper.tenstorrent.com/components/choicebox.md) when each option needs a description or a card-style target, [the `Toggle` component](https://vesper.tenstorrent.com/components/toggle.md) when a compact segmented control fits better, or [the `Select` component](https://vesper.tenstorrent.com/components/select.md) when the list is long enough that it should collapse into a dropdown. ## Examples ### Defining options Each entry in the `options` array describes a single radio input. `value` is what gets submitted and reported to `onChange`, while `label` is the text rendered beside the input: ```tsx demo import { RadioGroup } from "@tenstorrent/vesper/radio-group"; export default function BasicRadioGroup() { return ( ); } ``` Options are rendered in the order they are provided, and they all share the `name` you pass to the group, which is what makes the selection mutually exclusive. ### Uncontrolled vs controlled Render a `RadioGroup` in an uncontrolled fashion to let it keep track of its own selection. Pass `defaultValue` if one of the options should be selected initially: ```tsx demo import { RadioGroup } from "@tenstorrent/vesper/radio-group"; export default function UncontrolledRadioGroup() { return ( ); } ``` If you need to control which option is selected, you can do so via the `value` and `onChange` props. `onChange` receives the newly selected value directly: ```tsx demo import { useState } from "react"; import { RadioGroup } from "@tenstorrent/vesper/radio-group"; import { Typography } from "@tenstorrent/vesper/typography"; export default function ControlledRadioGroup() { const [value, setValue] = useState("md"); return (
Selected value: {value}
); } ``` ### Orientation Options are stacked vertically by default. Pass `orientation="horizontal"` to lay them out in a row, which suits short labels and small option sets: ```tsx demo import { RadioGroup } from "@tenstorrent/vesper/radio-group"; export default function HorizontalRadioGroup() { return ( ); } ``` ### Different sizes A `RadioGroup` can be rendered at `sm` or `md` size, defaulting to `md`. Size affects the dimensions of the radio inputs as well as the text styles of their labels: ```tsx demo import { RadioGroup } from "@tenstorrent/vesper/radio-group"; export default function RadioGroupSizes() { return (
); } ``` ### Disabling options Pass `disabled` to an individual option to prevent it from being selected while leaving the rest of the group interactive: ```tsx demo import { RadioGroup } from "@tenstorrent/vesper/radio-group"; export default function RadioGroupWithDisabledOption() { return ( ); } ``` Passing `disabled` to the group itself disables every option at once, which is useful while a form is submitting: ```tsx demo import { RadioGroup } from "@tenstorrent/vesper/radio-group"; export default function DisabledRadioGroup() { return ( ); } ``` ### Usage in forms `RadioGroup` renders native `` elements inside a `
`, so it works with regular form submission and native validation. The selected option submits its `value` under the group's `name`, and passing `required` refuses submission until one of the options is picked: ```tsx demo import { useState } from "react"; import { Button } from "@tenstorrent/vesper/button"; import { RadioGroup } from "@tenstorrent/vesper/radio-group"; import { Typography } from "@tenstorrent/vesper/typography"; export default function FormRadioGroupDemo() { const [submitted, setSubmitted] = useState(null); return ( { event.preventDefault(); const data = new FormData(event.currentTarget); setSubmitted(String(data.get("contact") ?? "")); }} > {submitted !== null && ( Submitted value: contact={submitted} )} ); } ``` > [!NOTE] > > Give the group an accessible name so assistive technology can announce what the options belong to. `RadioGroup` renders a `
`, so you can either pass it an `aria-label`, or point `aria-labelledby` at the heading or label that already names the group. --- Source: https://vesper.tenstorrent.com/components/range.md # Range A multi-thumb range input for selecting a span of numeric values between a minimum and maximum. It supports tick marks, value labels, and configurable stepping. ```tsx demo import { Range } from "@tenstorrent/vesper/range"; export default function RangeDemo() { return ( ); } ``` ## Options | Prop | Type | Description | Default | | ----------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------ | | `thumbAriaLabels` | `string[]` | Accessible `aria-label` attributes for each thumb, in the same order as the values. Required. | — | | `values` | `number[]` | The values of each thumb (controlled mode). The number of thumbs matches the length of this array. | — | | `defaultValues` | `number[]` | The initial thumb values (uncontrolled mode). | `[min, max]` | | `onValuesChange` | `(values: number[]) => void` | Callback fired as thumb values change during interaction. Receives the full array of current values. | — | | `onValuesCommit` | `(values: number[]) => void` | Callback fired when a thumb interaction is completed, eg. on pointer up. Receives the final array of values. | — | | `min` | `number` | The minimum allowed value. | `0` | | `max` | `number` | The maximum allowed value. | `100` | | `step` | `number` | The stepping interval between selectable values. | `1` | | `minStepsBetweenThumbs` | `number` | The minimum number of steps required between thumbs, preventing them from overlapping. | `1` | | `showTicks` | `boolean` | When `true`, renders tick marks along the track at each step interval. | `false` | | `showValueLabels` | `boolean` | When `true`, displays a value label above each thumb. | `false` | | `valueLabels` | `string[]` | Custom display labels for each thumb. When unset, each label falls back to that thumb's current value. | — | | `disabled` | `boolean` | When `true`, prevents interaction. | `false` | | `name` | `string` | The form field name submitted with form data. Every thumb submits its value under this name. | — | | `form` | `string` | Associates the range with a `
` element by its `id`. | — | All other props are forwarded to the wrapping `
` element. If you only need to select a single value, reach for [the `Slider` component](https://vesper.tenstorrent.com/components/slider.md) instead: it renders the same track, ticks, and labels, but exposes a single-value API. ## Examples ### Uncontrolled vs controlled Render a `Range` in an uncontrolled fashion to let it keep track of its own values. Pass `defaultValues` if the thumbs should start somewhere other than `min` and `max`: ```tsx demo import { Range } from "@tenstorrent/vesper/range"; export default function UncontrolledRange() { return ( ); } ``` If you need to control the values, use the `values` and `onValuesChange` props. `onValuesChange` fires continuously as a thumb is dragged, and always receives the full array of values: ```tsx demo import { useState } from "react"; import { Button } from "@tenstorrent/vesper/button"; import { Range } from "@tenstorrent/vesper/range"; import { Typography } from "@tenstorrent/vesper/typography"; export default function ControlledRange() { const [price, setPrice] = useState([25, 75]); return (
Selected values: {price.join(", ")}
); } ``` Use `onValuesCommit` when you only care about the final values of an interaction, eg. to avoid firing a request on every pointer move: ```tsx import { Range } from "@tenstorrent/vesper/range"; export default function PriceRange() { return ( fetchResults({ min, max })} thumbAriaLabels={["Price (min)", "Price (max)"]} /> ); } ``` ### Bounds and stepping `min`, `max`, and `step` describe the values a thumb can take, defaulting to `0`, `100`, and `1`. A `step` of `50` on a range of `0-1000` restricts thumbs to multiples of 50: ```tsx demo import { Range } from "@tenstorrent/vesper/range"; export default function LargeStepRange() { return ( ); } ``` The `step` also determines how far a thumb moves per arrow key press when it is focused. ### Keeping thumbs apart By default, thumbs must stay at least one step apart. Raise `minStepsBetweenThumbs` when a range needs a wider minimum span. The range below keeps its thumbs at least 20 apart: ```tsx demo import { Range } from "@tenstorrent/vesper/range"; export default function CustomStepBetweenThumbsRange() { return ( ); } ``` The value is measured in steps, not in units, so a `minStepsBetweenThumbs` of `2` on a range with a `step` of `5` keeps thumbs at least 10 apart. ### Showing ticks Pass `showTicks` to mark each step along the track. Ticks are rendered between `min` and `max`, so a range of `0-10` with a `step` of `1` renders nine of them: ```tsx demo import { Range } from "@tenstorrent/vesper/range"; export default function RangeWithTicks() { return ( ); } ``` > [!NOTE] > > Ticks are drawn per step, so a fine `step` on a wide range produces a dense, noisy track. Ticks work best when the range has a handful of stops, eg. a range of `0-100` with a `step` of `10`. ### Value labels Pass `showValueLabels` to render each thumb's current value above it as you drag: ```tsx demo import { Range } from "@tenstorrent/vesper/range"; export default function RangeShowingValueLabels() { return ( ); } ``` Use `valueLabels` to display something other than the raw value, such as a formatted or unit-suffixed one. Labels are matched to thumbs by index, and are only rendered when `showValueLabels` is also passed: ```tsx demo import { useState } from "react"; import { Range } from "@tenstorrent/vesper/range"; export default function CustomThumbLabelsRange() { const [price, setPrice] = useState([30, 70]); return ( `$${value}`)} thumbAriaLabels={["Price (min)", "Price (max)"]} /> ); } ``` > [!IMPORTANT] > > Value labels are presentational, and are not announced by assistive technology. > > Screen readers announce the underlying numeric value of a thumb instead. If a label carries meaning that the number alone doesn't, such as a currency or a unit, include it in the `thumbAriaLabels` too. ### More than two thumbs A `Range` renders one thumb per entry in `values` or `defaultValues`, so it isn't limited to two. Give every thumb an entry in `thumbAriaLabels`, and in `valueLabels` when you are customising labels: ```tsx demo import { Range } from "@tenstorrent/vesper/range"; export default function ThreeThumbsRange() { return ( ); } ``` ### Labels and accessible names `thumbAriaLabels` names the individual thumbs. It is required, and each thumb needs its own entry. A thumb without a label is announced with no indication of which end of the range it controls. ```tsx demo import { Range } from "@tenstorrent/vesper/range"; export default function RangeDemo() { return ( ); } ``` ### Keyboard interaction Each thumb is a native ``, so a `Range` is fully keyboard operable once a thumb is focused: | Key | Behaviour | | ------------------------- | -------------------------------------------------------------------- | | `ArrowRight` / `ArrowUp` | Increases the focused thumb by `step`. | | `ArrowLeft` / `ArrowDown` | Decreases the focused thumb by `step`. | | `Shift` + any arrow key | Moves the focused thumb by 10 units instead of `step`. | | `PageUp` / `PageDown` | Increases / decreases the focused thumb by 10 units. | | `Home` | Moves the focused thumb as far towards `min` as it is allowed to go. | | `End` | Moves the focused thumb as far towards `max` as it is allowed to go. | ### Disabling the range Pass `disabled` to prevent all interaction. A disabled `Range` still displays its current values, but its thumbs are skipped in the tab order and excluded from form data: ```tsx demo import { Range } from "@tenstorrent/vesper/range"; export default function DisabledRange() { return ( ); } ``` ### Usage in forms Each thumb renders a native ``, so passing a `name` includes every thumb's value in form submissions under that name. Since every thumb submits under the same `name`, the form below submits `price=20&price=80`. Read both values with `FormData.getAll`: ```tsx const data = new FormData(form); data.getAll("price"); // ["20", "80"] ``` ```tsx demo import { useState } from "react"; import { Button } from "@tenstorrent/vesper/button"; import { Range } from "@tenstorrent/vesper/range"; import { Typography } from "@tenstorrent/vesper/typography"; export default function FormRangeDemo() { const [submitted, setSubmitted] = useState(null); return ( { event.preventDefault(); const data = new FormData(event.currentTarget); setSubmitted(data.getAll("price").map(String)); }} > {submitted !== null && ( Submitted values: {submitted.join(", ")} )} ); } ``` --- Source: https://vesper.tenstorrent.com/components/select.md # Select A form-ready dropdown select component supporting controlled and uncontrolled usage with four visual variants. ```tsx demo import { Select } from "@tenstorrent/vesper/select"; export default function SelectDemo() { return ( ` element that holds the selected value. All other props are forwarded to the underlying `
); } ``` ### Different sizes The `Select` component can be rendered at `sm`, `md`, or `lg` size, defaulting to `md`. Size affects the height, padding, and text styles of the trigger, as well as the size of its icons. ```tsx demo import { Select } from "@tenstorrent/vesper/select"; const COUNTRIES = [ { label: "Canada", value: "ca" }, { label: "Japan", value: "jp" }, { label: "Norway", value: "no" }, ]; export default function SelectSizes() { return (
} placeholder="Select a region" options={[ { label: "United States", value: "us" }, { label: "Europe", value: "eu" }, { label: "Asia Pacific", value: "ap" }, ]} /> ); } ``` > [!NOTE] > > The trailing caret is rendered by the component itself, and flips between its open and closed states as the dropdown is toggled. Only the leading icon is configurable. ### Disabling the select Pass `disabled` to prevent all interaction with the trigger and its dropdown. A disabled `Select` still displays its current selection, but is skipped in the tab order and excluded from form data: ```tsx import { Select } from "@tenstorrent/vesper/select"; export default function DisabledSelect() { return ( {submitted !== null && ( Submitted value: {submitted} )} ); } ``` The value submitted is the `value` of the selected option, not the `label` displayed in the trigger. The form above submits `fruit=apple` when "Apple" is selected. ### Accessing the underlying element Use the `ref` prop when you need direct access to the underlying trigger ` {submitted !== null && ( Submitted value: {submitted} )} ); } ``` A range input always has a value, so a `Slider` submits its current value even if the user never touches it. The form above submits `volume=50` on an untouched slider. --- Source: https://vesper.tenstorrent.com/components/snippet.md # Snippet A compact code display component with a built-in copy-to-clipboard button. Ideal for displaying short commands, installation instructions, or single-line code snippets. ```tsx demo import { Snippet } from "@tenstorrent/vesper/snippet"; export default function SnippetDemo() { return yarn add @tenstorrent/vesper; } ``` ## Options | Prop | Type | Description | Default | | ---------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------- | ----------- | | `children` | `string` | The code snippet text to display. Also used as the value copied to the clipboard when the copy button is clicked. | — | | `variant` | `"default" \| "contrast"` | The visual style variant of the snippet. | `"default"` | Any additional props are forwarded to the underlying `
` element. ## Examples ### Basic usage Render a `Snippet` by wrapping string children with it: ```tsx demo import { Snippet } from "@tenstorrent/vesper/snippet"; export default function BasicSnippet() { return yarn add @tenstorrent/vesper; } ``` ### Variants A `Snippet` can be rendered in one of two variants: `"default"` or `"contrast"`. Use the `"contrast"` variant to render a `Snippet` with increased contrast: ```tsx demo import { Snippet } from "@tenstorrent/vesper/snippet"; export default function SnippetVariants() { return (
curl https://api.example.com/data curl https://api.example.com/data
); } ``` ### Multiline snippets `Snippet` preserves newlines in the content it renders, so you can also pass string with line breaks to render multiline snippets. You can use escaped newline characters with `\n`: ```tsx demo import { Snippet } from "@tenstorrent/vesper/snippet"; export default function MultilineSnippet() { return {"nvm use 24\nnode -v >> .nvmrc"}; } ``` You can also use template literals: ```tsx demo import { Snippet } from "@tenstorrent/vesper/snippet"; const INSTALL_COMMAND = `nvm use 24 node -v >> .nvmrc`; export default function TemplateLiteralSnippet() { return {INSTALL_COMMAND}; } ``` --- Source: https://vesper.tenstorrent.com/components/split-button.md # Split Button A compound button that combines a primary action button with a dropdown menu toggle. The primary button fires an action, while the secondary caret button opens a menu of alternative actions. ```tsx demo import { SplitButton } from "@tenstorrent/vesper/split-button"; export default function SplitButtonDemo() { return ( {} }, { text: "Save and publish", onSelect: () => {} }, ]} > Save ); } ``` > [!IMPORTANT] > > Keep the content of the action button short (1-3 words) and descriptive of the action being taken, the same way you would for [a `Button`](https://vesper.tenstorrent.com/components/button.md). The menu items beside it should be variations on that action, not unrelated ones. ## Options | Prop | Type | Description | Default | | --------------------- | ---------------------------------------- | --------------------------------------------------------------------------- | --------------- | | `children` | `ReactNode` | The content of the primary action button. | — | | `menuItems` | `MenuItemProps[]` | The list of menu items rendered in the dropdown. **Required.** | — | | `onClick` | `MouseEventHandler` | Callback fired when the primary action button is clicked. | — | | `size` | `"sm" \| "md" \| "lg"` | The size of both the action button and the menu toggle. | `"md"` | | `variant` | `"subtle" \| "contrast"` | The visual style variant applied to both buttons. | `"subtle"` | | `disabled` | `boolean` | When `true`, disables both buttons. | `false` | | `menuButtonAriaLabel` | `string` | An accessible `aria-label` for the menu toggle button. | `"Toggle menu"` | | `menuWidth` | `number` | The width of the dropdown menu in pixels. Scales with the base rem size. | `200` | | `menuSide` | `"top" \| "bottom" \| "left" \| "right"` | The preferred side of the split button to render the dropdown menu against. | `"bottom"` | | `menuSideOffset` | `number` | The distance in pixels from the split button to the dropdown menu. | `8` | | `menuAlign` | `"start" \| "center" \| "end"` | The alignment of the dropdown menu relative to the split button. | `"start"` | | `menuAlignOffset` | `number` | An offset in pixels from the aligned edge of the split button. | `0` | | `menuOpen` | `boolean` | Controls the open state of the dropdown menu (controlled mode). | — | | `defaultMenuOpen` | `boolean` | Whether the dropdown menu is open by default (uncontrolled mode). | `false` | | `onMenuOpenChange` | `(open: boolean) => void` | Callback fired when the dropdown menu's open state changes. | — | All other props are forwarded to the underlying `
` element that wraps the two buttons. Use a `SplitButton` when one action is the obvious default, but closely related variations of it should stay within reach. When there is no default action to promote, reach for [the `Menu` component](https://vesper.tenstorrent.com/components/menu.md) on its own; when there are no alternatives to offer, reach for [the `Button` component](https://vesper.tenstorrent.com/components/button.md). ## Examples ### Basic usage Render a `SplitButton` by giving it some children to label the action button, an `onClick` handler for that action, and the `menuItems` that make up its dropdown: ```tsx demo import { useState } from "react"; import { SplitButton } from "@tenstorrent/vesper/split-button"; import { Typography } from "@tenstorrent/vesper/typography"; export default function BasicSplitButton() { const [lastAction, setLastAction] = useState(null); return (
setLastAction("Save")} menuItems={[ { text: "Save as draft", onSelect: () => setLastAction("Draft") }, { text: "Save and publish", onSelect: () => setLastAction("Publish"), }, ]} > Save Last action: {lastAction ?? "none"}
); } ``` Clicking the action button fires `onClick` without ever opening the menu, and clicking the caret button opens the menu without firing `onClick`. ### Menu items `menuItems` takes the same items as [the `Menu` component](https://vesper.tenstorrent.com/components/menu.md), so each one can render an `icon`, a `description`, and one of the `"default"`, `"selected"`, `"danger"`, `"locked"`, or `"disabled"` styles: ```tsx demo import { Copy, Download, Trash } from "@tenstorrent/vesper/icons"; import { SplitButton } from "@tenstorrent/vesper/split-button"; export default function SplitButtonMenuItems() { return ( {}} menuItems={[ { text: "Export as CSV", icon: , description: "Includes every visible column", onSelect: () => {}, }, { text: "Copy to clipboard", icon: , onSelect: () => {}, }, { text: "Delete export", icon: , style: "danger", onSelect: () => {}, }, ]} > Export ); } ``` ### Variants A `SplitButton` can be rendered in one of two variants, which is applied to both of its buttons at once: | Variant | When to use | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | `"contrast"` | Use when the action needs strong visual contrast against its surrounding content, such as the main action of a toolbar. | | `"subtle"` | Default variant. Use when the action should sit alongside surrounding content without dominating it, such as a row-level action in a table. | ```tsx demo import { SplitButton } from "@tenstorrent/vesper/split-button"; const MENU_ITEMS = [ { text: "Save as draft", onSelect: () => {} }, { text: "Save and publish", onSelect: () => {} }, ]; export default function SplitButtonVariants() { return (
Contrast Subtle
); } ``` ### Different sizes A `SplitButton` can be rendered in one of three sizes; `"sm"`, `"md"`, or `"lg"`. The default size is `"md"`. ```tsx demo import { SplitButton } from "@tenstorrent/vesper/split-button"; const MENU_ITEMS = [ { text: "Save as draft", onSelect: () => {} }, { text: "Save and publish", onSelect: () => {} }, ]; export default function SplitButtonSizes() { return (
Small Medium Large
); } ``` ### Setting the menu width The dropdown is `200px` wide by default. Pass `menuWidth` when that is too narrow for its items, such as when they carry descriptions or unusually long labels. Like the menu offsets, this value scales with the base rem size: ```tsx demo import { SplitButton } from "@tenstorrent/vesper/split-button"; const MENU_ITEMS = [ { text: "Export as CSV", onSelect: () => {} }, { text: "Export as JSON", onSelect: () => {} }, ]; export default function SplitButtonMenuWidth() { return ( 320px width menu ); } ``` ### Positioning the menu The dropdown is positioned against the split button as a whole, not just the caret button that opens it, so a menu aligned to `"end"` lines up with the right edge of the action button. The `menuSide`, `menuSideOffset`, `menuAlign`, and `menuAlignOffset` props map onto [the `Menu` component](https://vesper.tenstorrent.com/components/menu.md)'s `side`, `sideOffset`, `align`, and `alignOffset` props: ```tsx demo import { SplitButton } from "@tenstorrent/vesper/split-button"; const MENU_ITEMS = [ { text: "Save as draft", onSelect: () => {} }, { text: "Save and publish", onSelect: () => {} }, ]; export default function SplitButtonMenuPlacement() { return (
Aligned to start Aligned to end Above the button
); } ``` ### Controlling the menu's open state By default, a `SplitButton` tracks its own menu state. Pass `defaultMenuOpen` to render with the menu already open, or use `menuOpen` and `onMenuOpenChange` together to own the state yourself: ```tsx demo import { useState } from "react"; import { SplitButton } from "@tenstorrent/vesper/split-button"; import { Typography } from "@tenstorrent/vesper/typography"; export default function ControlledSplitButtonMenu() { const [menuOpen, setMenuOpen] = useState(false); return (
{}} menuItems={[ { text: "Save as draft", onSelect: () => {} }, { text: "Save and publish", onSelect: () => {} }, ]} > Save Menu open state: {String(menuOpen)}
); } ``` ### Labelling the menu toggle The caret button has no visible text, so it is labelled `"Toggle menu"` for assistive technology by default. Pass `menuButtonAriaLabel` to override the default ARIA label: ```tsx demo import { SplitButton } from "@tenstorrent/vesper/split-button"; export default function LabelledSplitButton() { return ( {}} menuItems={[ { text: "Export as CSV", onSelect: () => {} }, { text: "Export as JSON", onSelect: () => {} }, ]} > Export ); } ``` ### Disabling a split button Pass `disabled` to disable both the action button and the caret button at the same time. The action button stops firing `onClick`, and the caret button no longer opens its menu: ```tsx demo import { SplitButton } from "@tenstorrent/vesper/split-button"; export default function DisabledSplitButton() { return ( {}} menuItems={[ { text: "Save as draft", onSelect: () => {} }, { text: "Save and publish", onSelect: () => {} }, ]} > Save ); } ``` > [!NOTE] > > There is no way to disable only one half of a `SplitButton`. When the alternatives are unavailable but the main action is not (or the other way around), render the individual items as `"disabled"` or `"locked"` [menu items](https://vesper.tenstorrent.com/components/menu.md) instead, so the reason stays visible to the user. --- Source: https://vesper.tenstorrent.com/components/status-indicator.md # Status Indicator A compact status label with a colored dot that communicates the current state of a process or resource. Supports five states and an optional pulsing animation. ```tsx demo import { StatusIndicator } from "@tenstorrent/vesper/status-indicator"; export default function StatusIndicatorDemo() { return ( ); } ``` ## Options | Prop | Type | Description | Default | | ---------- | ------------------------------------------------------------- | -------------------------------------------------------------------------- | ----------- | | `label` | `string` | The text label displayed next to the status dot. | — | | `state` | `"queued" \| "progress" \| "error" \| "ready" \| "cancelled"` | The current status state, which determines the color of the indicator dot. | — | | `variant` | `"default" \| "badge"` | The visual style variant of the status indicator. | `"default"` | | `animated` | `boolean` | When `true`, applies a pulsing animation to the status dot. | `false` | | `as` | `ElementType` | The root element type for polymorphic rendering. | `"div"` | Any additional props are forwarded to the wrapping `
` element, or whichever element is specified by the `as` prop. ## Examples ### Basic Usage Render a `StatusIndicator` by giving it a `state` and a `label`: ```tsx demo import { StatusIndicator } from "@tenstorrent/vesper/status-indicator"; export default function BasicStatusIndicator() { return ; } ``` ### Variants A `StatusIndicator` can be rendered in one of two visual variants: `"default"`, or `"badge"`. The default variant is `"default"`. When rendered using the `"badge"` variant, the `StatusIndicator` receives a subtle border, visually separating it from its surrounding content: ```tsx demo import { StatusIndicator } from "@tenstorrent/vesper/status-indicator"; export default function StatusIndicatorVariants() { return ( ); } ``` ### States `StatusIndicator` can be rendered with one of five states: `"queued"`, `"progress"`, `"error"`, `"ready"`, or `"cancelled"`. Use a `state` which corresponds with the status of the resource it describes. ```tsx demo import { StatusIndicator } from "@tenstorrent/vesper/status-indicator"; export default function StatusIndicatorStates() { return (
); } ``` ### Animating the indicator dot The colored dot next to the label can be rendered with a gentle pulsing animation by passing `animated={true}` or just `animated` as a prop: ```tsx demo import { StatusIndicator } from "@tenstorrent/vesper/status-indicator"; export default function AnimatedStatusIndicator() { return ; } ``` --- Source: https://vesper.tenstorrent.com/components/switch.md # Switch A toggle switch input with an optional label. Uses the native `switch` ARIA role for binary on/off states, with no indeterminate state. Supports controlled and uncontrolled modes. ```tsx demo import { Switch } from "@tenstorrent/vesper/switch"; export default function SwitchDemo() { return ; } ``` ## Options | Prop | Type | Description | Default | | ---------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------- | ------- | | `label` | `string` | The text label displayed next to the switch. An asterisk is appended when `required` is `true`. | — | | `size` | `"sm" \| "md"` | The size of the switch and its label. | `"md"` | | `ref` | `Ref` | A ref forwarded to the underlying `` element. | — | | `checked` | `boolean` | Controls the checked state (controlled mode). | — | | `defaultChecked` | `boolean` | The initial checked state (uncontrolled mode). | `false` | | `disabled` | `boolean` | When `true`, prevents interaction. | `false` | | `required` | `boolean` | When `true`, marks the input as required and appends an asterisk to the label. | `false` | | `onChange` | `ChangeEventHandler` | Callback fired when the switch is toggled. | — | Form and accessibility props such as `autoFocus`, `tabIndex`, `aria-label`, `aria-labelledby`, `aria-describedby`, `aria-invalid`, and event handlers like `onFocus`, `onBlur`, and `onKeyDown` are forwarded to the underlying `` element, as is `ref`. Any other props, including `className`, are forwarded to the wrapping `