Skip to main content
5Stack uses a comprehensive UI component library built on top of Reka UI (headless UI components) with custom styling using Tailwind CSS. The design system provides consistent, accessible, and themeable components.

Design System Foundation

Component Architecture

All UI components follow these principles:
  • Headless: Built on Reka UI for accessibility and behavior
  • Composable: Small, focused components that combine together
  • Styled: Tailwind CSS with custom theme variables
  • Type-safe: Full TypeScript support
  • Accessible: ARIA compliant with keyboard navigation
Location: components/ui/

Form Components

Button

Primary interaction component with multiple variants. Location: components/ui/button/Button.vue Usage Example:
<template>
  <Button variant="default" size="default" @click="handleClick">
    Click Me
  </Button>
  
  <Button variant="destructive" size="sm">
    Delete
  </Button>
  
  <Button variant="outline" size="lg" as-child>
    <NuxtLink to="/dashboard">Dashboard</NuxtLink>
  </Button>
</template>

<script setup>
import { Button } from '~/components/ui/button'
</script>
Props:
  • variant: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'
  • size: 'default' | 'sm' | 'lg' | 'icon'
  • as: HTML element or component to render as
  • asChild: Render as child element (for composition)
Variants:
variant: {
  default: 'bg-primary text-primary-foreground hover:bg-primary/90',
  destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
  outline: 'border border-input hover:bg-accent hover:text-accent-foreground',
  secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
  ghost: 'hover:bg-accent hover:text-accent-foreground',
  link: 'text-primary underline-offset-4 hover:underline'
}

Input

Text input field with validation support. Location: components/ui/input/Input.vue Usage Example:
<template>
  <Input
    v-model="username"
    type="text"
    placeholder="Enter username"
    class="max-w-sm"
  />
  
  <Input
    v-model="email"
    type="email"
    :disabled="isLoading"
  />
</template>

<script setup>
import { Input } from '~/components/ui/input'

const username = ref('')
const email = ref('')
const isLoading = ref(false)
</script>
Props:
  • modelValue: Input value (v-model)
  • defaultValue: Default value
  • type: Input type (text, email, password, etc.)
  • disabled: Disable input
  • Standard HTML input attributes

Form Components

Form components work with vee-validate for validation: Locations:
  • components/ui/form/FormItem.vue
  • components/ui/form/FormLabel.vue
  • components/ui/form/FormControl.vue
  • components/ui/form/FormDescription.vue
  • components/ui/form/FormMessage.vue
Usage Example:
<template>
  <form @submit.prevent="onSubmit">
    <FormField v-slot="{ componentField }" name="username">
      <FormItem>
        <FormLabel>Username</FormLabel>
        <FormControl>
          <Input
            type="text"
            placeholder="Enter username"
            v-bind="componentField"
          />
        </FormControl>
        <FormDescription>
          This is your public display name.
        </FormDescription>
        <FormMessage />
      </FormItem>
    </FormField>
    
    <Button type="submit">Submit</Button>
  </form>
</template>

<script setup>
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod'
import { Button } from '~/components/ui/button'
import { Input } from '~/components/ui/input'
import {
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '~/components/ui/form'

const formSchema = toTypedSchema(
  z.object({
    username: z.string().min(3).max(20),
  })
)

const form = useForm({
  validationSchema: formSchema,
})

const onSubmit = form.handleSubmit((values) => {
  console.log('Form submitted:', values)
})
</script>

Select

Dropdown select component. Location: components/ui/select/ Components:
  • Select.vue - Root component
  • SelectTrigger.vue - Trigger button
  • SelectContent.vue - Dropdown content
  • SelectGroup.vue - Option group
  • SelectItem.vue - Single option
  • SelectLabel.vue - Group label
  • SelectSeparator.vue - Visual separator
  • SelectValue.vue - Selected value display
Usage Example:
<template>
  <Select v-model="selectedValue">
    <SelectTrigger class="w-[180px]">
      <SelectValue placeholder="Select an option" />
    </SelectTrigger>
    <SelectContent>
      <SelectGroup>
        <SelectLabel>Options</SelectLabel>
        <SelectItem value="option1">Option 1</SelectItem>
        <SelectItem value="option2">Option 2</SelectItem>
        <SelectItem value="option3">Option 3</SelectItem>
      </SelectGroup>
    </SelectContent>
  </Select>
</template>

<script setup>
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from '~/components/ui/select'

const selectedValue = ref('option1')
</script>

Switch

Toggle switch component. Location: components/ui/switch/Switch.vue Usage Example:
<template>
  <div class="flex items-center space-x-2">
    <Switch v-model="isEnabled" id="airplane-mode" />
    <Label for="airplane-mode">Airplane Mode</Label>
  </div>
</template>

<script setup>
import { Switch } from '~/components/ui/switch'
import { Label } from '~/components/ui/label'

const isEnabled = ref(false)
</script>

Textarea

Multi-line text input. Location: components/ui/textarea/Textarea.vue Usage Example:
<template>
  <Textarea
    v-model="description"
    placeholder="Enter description"
    :rows="4"
  />
</template>

<script setup>
import { Textarea } from '~/components/ui/textarea'

const description = ref('')
</script>

RadioGroup

Radio button group component. Location: components/ui/radio-group/ Usage Example:
<template>
  <RadioGroup v-model="selectedOption">
    <div class="flex items-center space-x-2">
      <RadioGroupItem value="option1" id="r1" />
      <Label for="r1">Option 1</Label>
    </div>
    <div class="flex items-center space-x-2">
      <RadioGroupItem value="option2" id="r2" />
      <Label for="r2">Option 2</Label>
    </div>
  </RadioGroup>
</template>

<script setup>
import { RadioGroup, RadioGroupItem } from '~/components/ui/radio-group'
import { Label } from '~/components/ui/label'

const selectedOption = ref('option1')
</script>

Layout Components

Card

Container component for content grouping. Location: components/ui/card/ Components:
  • Card.vue - Root container
  • CardHeader.vue - Header section
  • CardTitle.vue - Title text
  • CardDescription.vue - Description text
  • CardContent.vue - Main content area
  • CardFooter.vue - Footer section
Usage Example:
<template>
  <Card>
    <CardHeader>
      <CardTitle>Card Title</CardTitle>
      <CardDescription>Card description goes here</CardDescription>
    </CardHeader>
    <CardContent>
      <p>Main content of the card</p>
    </CardContent>
    <CardFooter>
      <Button>Action</Button>
    </CardFooter>
  </Card>
</template>

<script setup>
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from '~/components/ui/card'
import { Button } from '~/components/ui/button'
</script>

Separator

Visual divider component. Location: components/ui/separator/Separator.vue Usage Example:
<template>
  <div>
    <div>Section 1</div>
    <Separator class="my-4" />
    <div>Section 2</div>
    
    <!-- Vertical separator -->
    <div class="flex h-5 items-center space-x-4">
      <div>Item 1</div>
      <Separator orientation="vertical" />
      <div>Item 2</div>
    </div>
  </div>
</template>

<script setup>
import { Separator } from '~/components/ui/separator'
</script>
Props:
  • orientation: 'horizontal' | 'vertical' (default: horizontal)
  • decorative: Whether separator is decorative only

Tabs

Tab navigation component. Location: components/ui/tabs/ Usage Example:
<template>
  <Tabs default-value="account" class="w-[400px]">
    <TabsList>
      <TabsTrigger value="account">Account</TabsTrigger>
      <TabsTrigger value="password">Password</TabsTrigger>
    </TabsList>
    <TabsContent value="account">
      <p>Account settings content</p>
    </TabsContent>
    <TabsContent value="password">
      <p>Password settings content</p>
    </TabsContent>
  </Tabs>
</template>

<script setup>
import {
  Tabs,
  TabsContent,
  TabsList,
  TabsTrigger,
} from '~/components/ui/tabs'
</script>

Table

Data table components. Location: components/ui/table/ Components:
  • Table.vue - Root table
  • TableHeader.vue - Header section
  • TableBody.vue - Body section
  • TableFooter.vue - Footer section
  • TableRow.vue - Table row
  • TableHead.vue - Header cell
  • TableCell.vue - Data cell
  • TableCaption.vue - Table caption
Usage Example:
<template>
  <Table>
    <TableCaption>A list of recent matches</TableCaption>
    <TableHeader>
      <TableRow>
        <TableHead>Team 1</TableHead>
        <TableHead>Score</TableHead>
        <TableHead>Team 2</TableHead>
      </TableRow>
    </TableHeader>
    <TableBody>
      <TableRow v-for="match in matches" :key="match.id">
        <TableCell>{{ match.team1 }}</TableCell>
        <TableCell>{{ match.score }}</TableCell>
        <TableCell>{{ match.team2 }}</TableCell>
      </TableRow>
    </TableBody>
  </Table>
</template>

<script setup>
import {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '~/components/ui/table'

const matches = ref([
  { id: 1, team1: 'Team A', score: '16-14', team2: 'Team B' },
  { id: 2, team1: 'Team C', score: '13-16', team2: 'Team D' },
])
</script>

Overlay Components

Dialog

Modal dialog component. Location: components/ui/dialog/ Components:
  • Dialog.vue - Root component
  • DialogTrigger.vue - Trigger button
  • DialogContent.vue - Dialog content
  • DialogHeader.vue - Header section
  • DialogFooter.vue - Footer section
  • DialogTitle.vue - Title text
  • DialogDescription.vue - Description text
  • DialogClose.vue - Close button
Usage Example:
<template>
  <Dialog v-model:open="isOpen">
    <DialogTrigger as-child>
      <Button>Open Dialog</Button>
    </DialogTrigger>
    <DialogContent>
      <DialogHeader>
        <DialogTitle>Confirm Action</DialogTitle>
        <DialogDescription>
          Are you sure you want to proceed?
        </DialogDescription>
      </DialogHeader>
      <DialogFooter>
        <DialogClose as-child>
          <Button variant="outline">Cancel</Button>
        </DialogClose>
        <Button @click="handleConfirm">Confirm</Button>
      </DialogFooter>
    </DialogContent>
  </Dialog>
</template>

<script setup>
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '~/components/ui/dialog'
import { Button } from '~/components/ui/button'

const isOpen = ref(false)

function handleConfirm() {
  // Handle confirmation
  isOpen.value = false
}
</script>

Popover

Popover component for contextual content. Location: components/ui/popover/ Usage Example:
<template>
  <Popover>
    <PopoverTrigger as-child>
      <Button variant="outline">Open Popover</Button>
    </PopoverTrigger>
    <PopoverContent class="w-80">
      <div class="grid gap-4">
        <h4 class="font-medium leading-none">Dimensions</h4>
        <p class="text-sm text-muted-foreground">
          Set the dimensions for the layer.
        </p>
      </div>
    </PopoverContent>
  </Popover>
</template>

<script setup>
import { Popover, PopoverContent, PopoverTrigger } from '~/components/ui/popover'
import { Button } from '~/components/ui/button'
</script>

Sheet

Slide-in panel component. Location: components/ui/sheet/ Usage Example:
<template>
  <Sheet>
    <SheetTrigger as-child>
      <Button>Open Sheet</Button>
    </SheetTrigger>
    <SheetContent>
      <SheetHeader>
        <SheetTitle>Settings</SheetTitle>
        <SheetDescription>
          Configure your settings here.
        </SheetDescription>
      </SheetHeader>
      <!-- Content -->
      <SheetFooter>
        <SheetClose as-child>
          <Button>Save</Button>
        </SheetClose>
      </SheetFooter>
    </SheetContent>
  </Sheet>
</template>

<script setup>
import {
  Sheet,
  SheetClose,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from '~/components/ui/sheet'
import { Button } from '~/components/ui/button'
</script>

Tooltip

Tooltip component for hints and descriptions. Location: components/ui/tooltip/ Usage Example:
<template>
  <TooltipProvider>
    <Tooltip>
      <TooltipTrigger as-child>
        <Button variant="outline">Hover me</Button>
      </TooltipTrigger>
      <TooltipContent>
        <p>Tooltip content here</p>
      </TooltipContent>
    </Tooltip>
  </TooltipProvider>
</template>

<script setup>
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from '~/components/ui/tooltip'
import { Button } from '~/components/ui/button'
</script>

Toast

Toast notification component. Location: components/ui/toast/ Usage Example:
<template>
  <Button @click="showToast">Show Toast</Button>
  <Toaster />
</template>

<script setup>
import { toast } from '~/components/ui/toast'
import { Button } from '~/components/ui/button'
import { Toaster } from '~/components/ui/toast'

function showToast() {
  toast({
    title: 'Success',
    description: 'Your action was completed successfully.',
    variant: 'default',
  })
}
</script>
Toast Options:
toast({
  title: string,
  description?: string,
  variant?: 'default' | 'destructive',
  duration?: number,  // milliseconds
  action?: {
    label: string,
    onClick: () => void
  }
})

Feedback Components

Badge

Inline status or label component. Location: components/ui/badge/Badge.vue Usage Example:
<template>
  <Badge variant="default">Default</Badge>
  <Badge variant="secondary">Secondary</Badge>
  <Badge variant="destructive">Destructive</Badge>
  <Badge variant="outline">Outline</Badge>
</template>

<script setup>
import { Badge } from '~/components/ui/badge'
</script>
Variants:
  • default: Primary badge
  • secondary: Secondary badge
  • destructive: Error/danger badge
  • outline: Outlined badge

Progress

Progress bar component. Location: components/ui/progress/Progress.vue Usage Example:
<template>
  <Progress :model-value="progress" class="w-[60%]" />
</template>

<script setup>
import { Progress } from '~/components/ui/progress'

const progress = ref(33)
</script>

Skeleton

Loading skeleton component. Location: components/ui/skeleton/Skeleton.vue Usage Example:
<template>
  <div class="flex items-center space-x-4">
    <Skeleton class="h-12 w-12 rounded-full" />
    <div class="space-y-2">
      <Skeleton class="h-4 w-[250px]" />
      <Skeleton class="h-4 w-[200px]" />
    </div>
  </div>
</template>

<script setup>
import { Skeleton } from '~/components/ui/skeleton'
</script>

Application sidebar with collapsible sections. Location: components/ui/sidebar/ Components (22 total):
  • Sidebar.vue - Root container
  • SidebarProvider.vue - Context provider
  • SidebarMenu.vue - Menu container
  • SidebarMenuItem.vue - Menu item
  • SidebarMenuButton.vue - Clickable menu item
  • And more…
Usage Example:
<template>
  <SidebarProvider>
    <Sidebar>
      <SidebarHeader>
        <h2>5Stack</h2>
      </SidebarHeader>
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupLabel>Navigation</SidebarGroupLabel>
          <SidebarGroupContent>
            <SidebarMenu>
              <SidebarMenuItem>
                <SidebarMenuButton as-child>
                  <NuxtLink to="/dashboard">Dashboard</NuxtLink>
                </SidebarMenuButton>
              </SidebarMenuItem>
            </SidebarMenu>
          </SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
    </Sidebar>
  </SidebarProvider>
</template>

Horizontal navigation menu. Location: components/ui/navigation-menu/
Menu bar component (File, Edit, etc.). Location: components/ui/menubar/

Pagination

Pagination controls for lists and tables. Location: components/ui/pagination/ Components:
  • PaginationFirst.vue - First page
  • PaginationPrev.vue - Previous page
  • PaginationNext.vue - Next page
  • PaginationLast.vue - Last page
  • PaginationEllipsis.vue - Ellipsis (…)

Utility Components

ScrollArea

Custom scrollbar component. Location: components/ui/scroll-area/ Usage Example:
<template>
  <ScrollArea class="h-[200px] w-[350px] rounded-md border">
    <div class="p-4">
      <h4 class="mb-4 text-sm font-medium">Tags</h4>
      <div v-for="tag in tags" :key="tag" class="text-sm">
        {{ tag }}
      </div>
    </div>
  </ScrollArea>
</template>

<script setup>
import { ScrollArea } from '~/components/ui/scroll-area'

const tags = Array.from({ length: 50 }).map(
  (_, i) => `Tag ${i + 1}`
)
</script>

NumberField

Numeric input with increment/decrement buttons. Location: components/ui/number-field/

Calendar

Date picker calendar component. Location: components/ui/calendar/Calendar.vue

Transitions

PageTransition

Page transition animations. Location: components/ui/transitions/PageTransition.vue

Utility Functions

cn (classname utility)

Merge Tailwind classes with conflict resolution:
import { cn } from '@/lib/utils'

const classes = cn(
  'bg-primary text-white',
  'hover:bg-primary/90',
  props.class
)

Theme System

The UI components use CSS variables for theming: Location: assets/css/tailwind.css
:root {
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --primary: 221.2 83.2% 53.3%;
  --primary-foreground: 210 40% 98%;
  --secondary: 210 40% 96.1%;
  --secondary-foreground: 222.2 47.4% 11.2%;
  --muted: 210 40% 96.1%;
  --muted-foreground: 215.4 16.3% 46.9%;
  --accent: 210 40% 96.1%;
  --accent-foreground: 222.2 47.4% 11.2%;
  --destructive: 0 84.2% 60.2%;
  --destructive-foreground: 210 40% 98%;
  --border: 214.3 31.8% 91.4%;
  --input: 214.3 31.8% 91.4%;
  --ring: 221.2 83.2% 53.3%;
  --radius: 0.5rem;
}

.dark {
  --background: 222.2 84% 4.9%;
  --foreground: 210 40% 98%;
  /* ... */
}

Accessibility

All UI components follow WCAG 2.1 Level AA guidelines:
  • Keyboard Navigation: Full keyboard support
  • Screen Readers: Proper ARIA labels and roles
  • Focus Management: Visible focus indicators
  • Color Contrast: Meets contrast requirements

Match Components

Match-specific components

Tournament Components

Tournament brackets and management

Resources

Reka UI

Headless UI component library

Tailwind CSS

Utility-first CSS framework

Build docs developers (and LLMs) love