> ## Documentation Index
> Fetch the complete documentation index at: https://docs.deploystack.io/llms.txt
> Use this file to discover all available pages before exploring further.

# UI Design System

> Comprehensive guide to UI components, styling patterns, and design standards for the DeployStack frontend.

This document establishes the official UI design patterns and component standards for the DeployStack frontend. All new components and pages must follow these guidelines to ensure consistency and maintainability.

## Design Principles

* **Consistency**: Use established patterns and components
* **Accessibility**: Follow WCAG guidelines and semantic HTML
* **Responsiveness**: Design for all screen sizes
* **Performance**: Optimize for fast loading and smooth interactions
* **Maintainability**: Write clean, reusable component code

## Color System

### Primary Colors

The DeployStack color palette uses neutral black as the primary color, following the default shadcn design system for a clean, professional appearance.

```css theme={null}
/* Primary Brand Colors */
--primary: hsl(240 5.9% 10%);     /* neutral-900 - Dark gray/black */
--primary-foreground: hsl(0 0% 98%);  /* White text on primary */
```

#### WCAG Compliance

* **Primary (neutral-900)**: Contrast ratio \~16:1 on white - ✅ AA Pass, ✅ AAA Pass
* **Dark mode primary (white)**: Contrast ratio \~16:1 on dark background - ✅ AA Pass, ✅ AAA Pass

This ensures excellent accessibility with high contrast ratios in both light and dark modes.

### Gray Colors

<Warning>
  **Only use Tailwind's `neutral` gray palette.** Do not use `zinc`, `gray`, `slate`, or `stone` for gray colors. This ensures consistent gray tones across the application matching our design system (#EBEBEB = neutral-200).
</Warning>

```css theme={null}
/* Approved gray scale */
neutral-50   /* #fafafa - Page backgrounds */
neutral-100  /* #f5f5f5 */
neutral-200  /* #e5e5e5 - Borders, dividers */
neutral-300  /* #d4d4d4 */
neutral-400  /* #a3a3a3 - Muted text */
neutral-500  /* #737373 */
neutral-600  /* #525252 - Secondary text */
neutral-700  /* #404040 */
neutral-800  /* #262626 - Primary text */
neutral-900  /* #171717 */
```

### Text Colors

```css theme={null}
/* Light Mode / Dark Mode */
--text-primary: theme('colors.neutral.800') / theme('colors.neutral.100');
--text-secondary: theme('colors.neutral.600') / theme('colors.neutral.400');
```

### Background Colors

```css theme={null}
/* Light Mode / Dark Mode */
--bg-primary: white / theme('colors.neutral.900');
--bg-secondary: theme('colors.neutral.50') / theme('colors.neutral.800');
```

### Link Utility Class

DeployStack provides a `.link` utility class for styled text links. This is an opt-in approach - links are unstyled by default to avoid conflicts with navigation components and buttons.

#### The `.link` Class

Apply the `.link` class to anchor elements when you want styled text links:

```html theme={null}
<a href="https://docs.deploystack.io" target="_blank" class="link">
  View documentation
</a>
```

**Styling applied:**

* `text-blue-600` - Blue color
* `underline underline-offset-4` - Underline with offset
* `hover:text-blue-800` - Darker blue on hover

#### Usage Examples

```html theme={null}
<!-- ✅ Styled text link with .link class -->
<a href="/about" class="link">Learn more about us</a>

<!-- ✅ External link with .link class -->
<span>
  Read more in our <a href="https://docs.deploystack.io" target="_blank" class="link">documentation</a>.
</span>

<!-- ✅ Unstyled link (default behavior) -->
<a href="/dashboard">Dashboard</a>

<!-- ✅ Button-styled link (no .link class needed) -->
<a href="/signup" class="rounded-md bg-primary px-4 py-2 text-white">Sign Up</a>
```

#### When to Use `.link`

| Use Case                        | Use `.link`? |
| ------------------------------- | ------------ |
| Inline text links in paragraphs | ✅ Yes        |
| Documentation/help links        | ✅ Yes        |
| External links                  | ✅ Yes        |
| Navigation menu items           | ❌ No         |
| Button-styled links             | ❌ No         |
| Sidebar/header links            | ❌ No         |

### Color Usage Guidelines

1. **Primary Actions**: Use `--primary` (neutral-900) for primary buttons and key interactive elements
2. **Hover States**: Use slightly lighter/darker variants for hover feedback
3. **Text Links**: Add `.link` class for blue styled links in content areas
4. **Button Links**: Include styling classes (`bg-*`, `rounded-*`) to maintain button appearance
5. **Focus States**: Ensure all interactive elements have visible focus indicators

## Layout Design Patterns

### Content Wrapper Pattern

DeployStack follows a **mandatory content wrapper pattern** for all tabbed content and detail pages. This pattern ensures visual consistency and proper content hierarchy throughout the application.

#### Design Requirements

The content wrapper pattern is **required** for:

* Team management pages
* MCP server installation pages
* Settings and configuration pages
* Any page using tabbed content with `DsTabs`
* Detail views that need elevated content presentation

#### Implementation

Use the `ContentWrapper` component for all qualifying pages:

```vue theme={null}
<ContentWrapper>
  <YourTabContent />
</ContentWrapper>
```

The wrapper provides:

* Gray background container (`bg-muted/50`)
* Responsive max-width constraints
* White card elevation with proper spacing
* Consistent vertical rhythm

For complete implementation details, see the component source code at `services/frontend/src/components/ContentWrapper.vue`.

#### Visual Hierarchy

This pattern creates a three-tier visual hierarchy:

1. **Page background** - Default dashboard background
2. **Content container** - Gray muted background wrapper
3. **Content card** - White elevated card with content

This hierarchy is a **design system requirement** and must be followed consistently across all applicable pages.

## Data Tables

For data table implementation, see the dedicated [Table Design System](/development/frontend/ui/design-system-table) guide.

For pagination implementation, see the [Pagination Implementation Guide](/development/frontend/ui/design-system-pagination).

## Badge Design Patterns

Badges are used for status indicators, categories, and metadata.

### Status Badges

```html theme={null}
<Badge variant="default">Active</Badge>
<Badge variant="secondary">Inactive</Badge>
<Badge variant="destructive">Error</Badge>
<Badge variant="outline">Pending</Badge>
```

### Category/Tag Badges

```html theme={null}
<Badge variant="secondary" class="font-mono text-xs">
  {{ category.icon }}
</Badge>
```

### Numeric Badges

```html theme={null}
<Badge variant="outline">
  {{ item.sort_order }}
</Badge>
```

## Dialog Patterns

### Confirmation Dialogs

For simple yes/no confirmation dialogs (delete confirmations, enable/disable toggles, destructive actions), always use `AlertDialog`. This component interrupts the user with important content and expects a response.

```vue theme={null}
<AlertDialog :open="isOpen" @update:open="(value) => isOpen = value">
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Are you sure?</AlertDialogTitle>
      <AlertDialogDescription>
        This action cannot be undone.
      </AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>Cancel</AlertDialogCancel>
      <AlertDialogAction @click="handleConfirm">
        Continue
      </AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>
```

#### When to Use AlertDialog

* Delete confirmations
* Enable/disable toggles
* Destructive actions that cannot be undone
* Any action requiring explicit user confirmation

#### Extracting Reusable Dialogs

For dialogs used in multiple places, extract them as reusable components in `components/mcp-server/` or similar directories. Examples:

* `McpServerDeleteDialog.vue` - Delete confirmation for MCP servers
* `McpServerStatusDialog.vue` - Enable/disable confirmation

## Form Design Patterns

### Modal Forms

Use `AlertDialog` for forms in modals:

```html theme={null}
<AlertDialog :open="isOpen" @update:open="(value) => isOpen = value">
  <AlertDialogContent class="sm:max-w-[425px]">
    <AlertDialogHeader>
      <AlertDialogTitle>{{ modalTitle }}</AlertDialogTitle>
      <AlertDialogDescription>
        {{ modalDescription }}
      </AlertDialogDescription>
    </AlertDialogHeader>

    <form @submit.prevent="handleSubmit" class="space-y-4">
      <!-- Form fields -->
      <div class="space-y-2">
        <Label for="field-name">{{ t('form.field.label') }}</Label>
        <Input
          id="field-name"
          v-model="formData.name"
          :placeholder="t('form.field.placeholder')"
          :class="{ 'border-destructive': errors.name }"
          required
        />
        <div v-if="errors.name" class="text-sm text-destructive">
          {{ errors.name }}
        </div>
      </div>

      <AlertDialogFooter>
        <Button type="button" variant="outline" @click="handleCancel">
          {{ t('form.cancel') }}
        </Button>
        <Button type="submit" :disabled="!isFormValid || isSubmitting">
          {{ isSubmitting ? t('form.saving') : t('form.save') }}
        </Button>
      </AlertDialogFooter>
    </form>
  </AlertDialogContent>
</AlertDialog>
```

### Form Field Pattern

```html theme={null}
<div class="space-y-2">
  <Label for="field-id">{{ t('form.field.label') }}</Label>
  <Input
    id="field-id"
    v-model="formData.field"
    :placeholder="t('form.field.placeholder')"
    :class="{ 'border-destructive': errors.field }"
    @input="handleFieldChange"
  />
  <div v-if="errors.field" class="text-sm text-destructive">
    {{ errors.field }}
  </div>
</div>
```

## Loading State Patterns

Choose the appropriate loading indicator based on what's being loaded:

### Content Loading → Use Skeleton

When loading page content, data lists, or complex UI sections, use `Skeleton` components that match the shape of the content being loaded. This provides a better user experience by showing users what to expect.

```vue theme={null}
<script setup lang="ts">
import { Skeleton } from '@/components/ui/skeleton'
</script>

<template>
  <!-- Loading state mimics actual content structure -->
  <div v-if="isLoading" class="space-y-4">
    <Skeleton class="h-4 w-64" />           <!-- Title placeholder -->
    <Skeleton class="h-4 w-48" />           <!-- Subtitle placeholder -->
    <div class="flex gap-2">
      <Skeleton class="h-10 w-10 rounded" /> <!-- Icon placeholder -->
      <Skeleton class="h-10 w-full" />       <!-- Content placeholder -->
    </div>
  </div>

  <!-- Actual content -->
  <div v-else>
    <!-- Real content here -->
  </div>
</template>
```

Use Skeleton for:

* Page content loading
* Data tables and lists
* Cards and detail views
* Form sections loading from API
* Any content where you can predict the layout

### Button Actions → Use Spinner

For button clicks and quick actions, use the built-in button loading state with a spinner. This indicates the action is processing without disrupting the page layout.

```vue theme={null}
<Button :loading="isSubmitting" loading-text="Saving...">
  Save Changes
</Button>
```

Use Spinner for:

* Form submissions
* Button click actions
* Quick API calls (enable/disable toggles)
* Actions that don't change page structure

### Why Skeleton Over Spinner for Content

| Aspect           | Skeleton                  | Spinner                        |
| ---------------- | ------------------------- | ------------------------------ |
| User expectation | Shows content shape       | Shows "something is happening" |
| Layout shift     | Minimal (matches content) | Can cause layout shift         |
| Perceived speed  | Feels faster              | Can feel slower                |
| Use case         | Content loading           | Action processing              |

## Empty State Patterns

**MANDATORY**: Always use the shadcn-vue `Empty` component for no-data states. Never create custom empty state markup with manual styling.

### Basic Empty State

```vue theme={null}
<script setup lang="ts">
import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription } from '@/components/ui/empty'
import { Package } from 'lucide-vue-next'
</script>

<template>
  <Empty v-if="!hasData">
    <EmptyHeader>
      <EmptyMedia variant="icon">
        <Package />
      </EmptyMedia>
      <EmptyTitle>No data found</EmptyTitle>
      <EmptyDescription>
        There is currently no data to display.
      </EmptyDescription>
    </EmptyHeader>
  </Empty>
</template>
```

### Empty State with Actions

```vue theme={null}
<script setup lang="ts">
import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent } from '@/components/ui/empty'
import { Button } from '@/components/ui/button'
import { Package, Plus } from 'lucide-vue-next'
</script>

<template>
  <Empty v-if="!hasData">
    <EmptyHeader>
      <EmptyMedia variant="icon">
        <Package />
      </EmptyMedia>
      <EmptyTitle>No items found</EmptyTitle>
      <EmptyDescription>
        Get started by creating your first item.
      </EmptyDescription>
    </EmptyHeader>
    <EmptyContent>
      <Button @click="handleCreate">
        <Plus class="h-4 w-4 mr-2" />
        Create Item
      </Button>
    </EmptyContent>
  </Empty>
</template>
```

Use Empty component for:

* No search results
* Empty data tables
* No tools/resources discovered
* Missing configuration items
* Any state where data is expected but not present

## Button Patterns

### Loading States

Buttons now include built-in loading state functionality. For comprehensive loading button documentation, see the [Button Loading States Guide](/development/frontend/ui/design-button-loading).

```html theme={null}
<!-- Button with loading state -->
<Button 
  :loading="isSubmitting"
  loading-text="Saving..."
  @click="handleSave"
>
  Save Changes
</Button>
```

### Primary Actions

```html theme={null}
<Button @click="handlePrimaryAction">
  <Plus class="h-4 w-4 mr-2" />
  {{ t('actions.add') }}
</Button>
```

### Secondary Actions

```html theme={null}
<Button variant="outline" @click="handleSecondaryAction">
  {{ t('actions.cancel') }}
</Button>
```

### Destructive Actions

```html theme={null}
<Button 
  variant="destructive" 
  @click="handleDelete"
  :loading="isDeleting"
  loading-text="Deleting..."
  class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
  <Trash2 v-if="!isDeleting" class="h-4 w-4 mr-2" />
  {{ t('actions.delete') }}
</Button>
```

### Icon-Only Buttons

```html theme={null}
<Button 
  variant="ghost" 
  size="icon"
  :loading="isRefreshing"
>
  <span class="sr-only">{{ t('actions.menu') }}</span>
  <MoreHorizontal v-if="!isRefreshing" class="h-4 w-4" />
</Button>
```

## Layout Patterns

### Page Header

```html theme={null}
<div class="flex items-center justify-between">
  <div>
    <h1 class="text-2xl font-bold">{{ pageTitle }}</h1>
    <p class="text-muted-foreground">{{ pageDescription }}</p>
  </div>
  <Button @click="handlePrimaryAction" class="flex items-center gap-2">
    <Plus class="h-4 w-4" />
    {{ t('actions.add') }}
  </Button>
</div>
```

### Content Sections

```html theme={null}
<div class="space-y-6">
  <!-- Header -->
  <div class="flex items-center justify-between">
    <!-- Header content -->
  </div>

  <!-- Success/Error Messages -->
  <Alert v-if="successMessage" class="border-green-200 bg-green-50 text-green-800">
    <CheckCircle class="h-4 w-4" />
    <AlertDescription>{{ successMessage }}</AlertDescription>
  </Alert>

  <!-- Main Content -->
  <div class="space-y-4">
    <!-- Content -->
  </div>
</div>
```

## Icon Usage

### Standard Icon Sizes

* **Small icons**: `h-4 w-4` (16px) - for buttons, table actions
* **Medium icons**: `h-5 w-5` (20px) - for form fields, navigation
* **Large icons**: `h-6 w-6` (24px) - for page headers, prominent actions

### Icon with Text

```html theme={null}
<Button>
  <Settings class="h-4 w-4 mr-2" />
  {{ t('actions.settings') }}
</Button>
```

### Status Icons

```html theme={null}
<CheckCircle class="h-4 w-4 text-green-600" />
<AlertCircle class="h-4 w-4 text-yellow-600" />
<XCircle class="h-4 w-4 text-red-600" />
```

## Responsive Design

### Mobile-First Approach

```html theme={null}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  <!-- Responsive grid -->
</div>
```

### Hide/Show on Different Screens

```html theme={null}
<div class="hidden md:block">Desktop only</div>
<div class="block md:hidden">Mobile only</div>
```

## Accessibility Guidelines

### Screen Reader Support

```html theme={null}
<Button>
  <span class="sr-only">{{ t('actions.openMenu') }}</span>
  <MoreHorizontal class="h-4 w-4" />
</Button>
```

### Proper Labels

```html theme={null}
<Label for="input-id">{{ t('form.label') }}</Label>
<Input id="input-id" v-model="value" />
```

### Focus Management

```html theme={null}
<Button 
  @click="handleAction"
  :disabled="isLoading"
  class="focus:ring-2 focus:ring-ring focus:ring-offset-2"
>
  {{ t('actions.submit') }}
</Button>
```

## Migration Guide

### Updating Existing Tables

If you have an existing table using raw HTML elements, follow these steps:

1. **Replace HTML elements** with shadcn-vue components:
   * `<table>` → `<Table>`
   * `<thead>` → `<TableHeader>`
   * `<tbody>` → `<TableBody>`
   * `<tr>` → `<TableRow>`
   * `<th>` → `<TableHead>`
   * `<td>` → `<TableCell>`

2. **Update imports**:
   ```html theme={null}
   import {
     Table,
     TableBody,
     TableCell,
     TableHead,
     TableHeader,
     TableRow,
   } from '@/components/ui/table'
   ```

3. **Add proper empty state handling**

4. **Update action menus** to use AlertDialog for destructive actions

5. **Ensure proper badge usage** for status indicators

For detailed migration strategies and architectural considerations, see the [Frontend Architecture - Migration Guidelines](/development/frontend/architecture#migration-guidelines).
