Skip to main content
Spacedrive’s UI is built on a set of reusable primitives from @sd/ui. These components provide consistent styling and behavior across the application.

Design Principles

Composition Over Configuration

Primitives are simple, composable building blocks rather than complex configured components.
// Complex configuration
<DataTable
  columns={...}
  data={...}
  filters={...}
  pagination={...}
/>

// Composable primitives
<div className="overflow-hidden rounded-lg border border-app-line">
  <table className="w-full">
    <thead className="bg-app-box">
      <tr>
        <th className="px-4 py-3 text-xs text-ink-dull">Name</th>
      </tr>
    </thead>
    <tbody className="divide-y divide-app-line">
      {data.map(item => (
        <tr className="bg-app-input/30">
          <td className="px-4 py-3 text-ink">{item.name}</td>
        </tr>
      ))}
    </tbody>
  </table>
</div>

Semantic Color Usage

All primitives use semantic color tokens, never raw Tailwind colors.
// Raw colors
<div className="bg-gray-800 text-gray-200">

// Semantic colors
<div className="bg-app-box text-ink">

Consistent Patterns

Common patterns are standardized across primitives: Card Pattern:
<div className="relative overflow-hidden rounded-2xl bg-sidebar/90 backdrop-blur-xl shadow-2xl">
  <div className="absolute top-0 h-px w-full bg-gradient-to-r from-transparent via-[#2D2D37]/60 to-transparent" />
  <div className="absolute bottom-0 h-px w-full bg-gradient-to-r from-transparent via-[#2D2D37]/60 to-transparent" />

  <div className="noise noise-faded noise-sm p-6">
    {/* Content */}
  </div>
</div>
List Item Pattern:
<div className="rounded-lg border border-app-line bg-app-input/30 p-4 hover:bg-app-input/40">
  {/* Content */}
</div>
Table Pattern:
<div className="overflow-hidden rounded-lg border border-app-line">
  <table className="w-full">
    <thead className="bg-app-box">
      <tr>
        <th className="px-4 py-3 text-xs font-medium text-gray-400">
          Column
        </th>
      </tr>
    </thead>
    <tbody className="divide-y divide-app-line">
      <tr className="bg-app-input/30 hover:bg-app-input/50">
        <td className="px-4 py-3 text-ink">Data</td>
      </tr>
    </tbody>
  </table>
</div>

Core Primitives

Button

Versatile button component with multiple variants and sizes.
import { Button } from '@sd/ui';

<Button variant="accent" size="md">
  Primary Action
</Button>

<Button variant="gray" size="sm">
  Secondary Action
</Button>

<Button variant="default" size="lg">
  Tertiary Action
</Button>
Variants:
  • default - Transparent with border, hover/active states
  • gray - App button background with hover/focus states
  • accent - Accent blue background with white text
  • subtle - Transparent border, subtle hover
  • outline - Sidebar line border style
  • dotted - Dashed border for add/create actions
  • colored - Custom colored backgrounds (pass bg color class)
  • bare - No styling whatsoever
Sizes:
  • xs - Extra small (px-1.5 py-0.5, text-xs)
  • sm - Small (px-2 py-0.5, text-sm) - default
  • md - Medium (px-2.5 py-1.5, text-sm)
  • lg - Large (px-3 py-1.5, text-md)
  • icon - Square icon button (!p-1)
Best Practice: Wrap icons and text in flex containers to prevent stacking:
<Button className="flex items-center gap-2">
  <Icon size={16} weight="fill" />
  <span>Label</span>
</Button>

Input

Form input with semantic styling and size variants.
import { Input, Label } from '@sd/ui';

<div>
  <Label>Username</Label>
  <Input
    placeholder="Enter username"
    size="lg"
    error={hasError}
  />
</div>
Variants:
  • default - Standard input with border and background
  • transparent - Transparent background, no border on focus
Sizes:
  • xs - 25px height
  • sm - 30px height (default)
  • md - 36px height
  • lg - 42px height
  • xl - 48px height
Props:
  • error - Shows error state (red border/ring)
  • icon - Icon component or React node
  • iconPosition - 'left' | 'right' (default: 'left')
  • right - React node to display on the right side
  • inputElementClassName - Additional classes for the input element itself
Additional Components:
  • SearchInput - Input with MagnifyingGlass icon pre-configured
  • PasswordInput - Input with eye icon toggle for show/hide password
  • TextArea - Multi-line text input with same styling system
  • Label - Semantic label component with slug prop for htmlFor

Form Components

React Hook Form integration with automatic validation display.
import { Form, InputField, z } from '@sd/ui/src/forms';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';

const schema = z.object({
  username: z.string().min(3),
  email: z.string().email(),
});

function MyForm() {
  const form = useForm({
    resolver: zodResolver(schema),
  });

  return (
    <Form form={form} onSubmit={form.handleSubmit(onSubmit)}>
      <InputField
        name="username"
        label="Username"
        placeholder="Enter username"
      />
      <InputField
        name="email"
        label="Email"
        type="email"
      />
      <Button type="submit">Submit</Button>
    </Form>
  );
}

Switch

Toggle switch for boolean settings.
import { Switch } from '@sd/ui';

const [enabled, setEnabled] = useState(false);

<div className="flex items-center justify-between">
  <div>
    <div className="text-sm text-ink">Enable Feature</div>
    <div className="text-xs text-ink-dull">Description</div>
  </div>
  <Switch checked={enabled} onCheckedChange={setEnabled} />
</div>

ShinyToggle

Animated toggle component for switching between multiple options with a smooth glowing indicator.
import { ShinyToggle } from '@sd/ui';

const [view, setView] = useState<'grid' | 'list'>('grid');

<ShinyToggle
  value={view}
  onChange={setView}
  options={[
    { value: 'grid', label: 'Grid', count: 42 },
    { value: 'list', label: 'List', count: 42 },
  ]}
/>
Features:
  • Smooth animated indicator using Framer Motion
  • Gradient background with glow effect
  • Optional count badges
  • Type-safe with generics
  • Fully accessible
Props:
  • value - Current selected value (generic type T)
  • onChange - Callback when selection changes
  • options - Array of { value: T, label: ReactNode, count?: number }
  • className - Additional classes for the container
Context menu and dropdown with Radix UI.
import { DropdownMenu } from '@sd/ui';

<DropdownMenu.Root
  trigger={
    <button>Open Menu</button>
  }
>
  <DropdownMenu.Item
    label="Action"
    icon={IconComponent}
    onClick={handleClick}
  />
  <DropdownMenu.Separator />
  <DropdownMenu.Item
    label="Delete"
    icon={TrashIcon}
    variant="danger"
  />
</DropdownMenu.Root>

Glassmorphism Effect

Spacedrive’s signature glassmorphism effect combines backdrop blur, transparency, and gradient borders.
<div className="relative overflow-hidden rounded-2xl bg-sidebar/90 backdrop-blur-xl shadow-2xl">
  {/* Top gradient border */}
  <div className="absolute top-0 h-px w-full bg-gradient-to-r from-transparent via-[#2D2D37]/60 to-transparent" />

  {/* Bottom gradient border */}
  <div className="absolute bottom-0 h-px w-full bg-gradient-to-r from-transparent via-[#2D2D37]/60 to-transparent" />

  {/* Content with noise texture */}
  <div className="noise noise-faded noise-sm p-6">
    Content
  </div>
</div>
Noise Variants:
  • noise - Base noise texture
  • noise-faded - Faded intensity
  • noise-sm - Small grain size

Progress Bars

Consistent progress bar pattern for resource usage.
<div>
  <div className="mb-2 flex items-center justify-between text-xs">
    <span className="text-ink-dull">Storage</span>
    <span className="text-ink">45/100 GB</span>
  </div>
  <div className="h-2 overflow-hidden rounded-full bg-app-box">
    <div
      className="h-full bg-accent"
      style={{ width: '45%' }}
    />
  </div>
</div>
Color by type:
  • Storage: bg-accent (blue)
  • AI/Compute: bg-purple-500
  • Bandwidth: bg-green-500
  • Progress: bg-blue-500
  • Success: bg-green-400

Status Badges

Standard status badge pattern.
const STATUS_CONFIG = {
  running: { color: 'text-green-400', bg: 'bg-green-500/20' },
  stopped: { color: 'text-gray-400', bg: 'bg-gray-500/20' },
  error: { color: 'text-red-400', bg: 'bg-red-500/20' },
};

<div className={`flex items-center gap-1.5 rounded-full px-2.5 py-1 ${STATUS_CONFIG.running.bg}`}>
  <div className="h-1.5 w-1.5 rounded-full bg-green-400" />
  <span className={`text-xs font-medium ${STATUS_CONFIG.running.color}`}>
    Running
  </span>
</div>

Empty States

Pattern for when lists/grids are empty.
<div className="rounded-lg border border-dashed border-app-line bg-app-box/50 p-12 text-center">
  <Icon size={48} weight="fill" className="mx-auto mb-3 text-ink-dull" />
  <h3 className="mb-1 text-lg font-semibold text-white">
    No items yet
  </h3>
  <p className="mb-4 text-sm text-ink-dull">
    Description of what would appear here
  </p>
  <Button variant="accent" size="lg">
    Create First Item
  </Button>
</div>

Gradients

Background Gradients

<div className="bg-gradient-to-br from-accent to-blue-600">
  Icon background
</div>

<div className="bg-gradient-to-b from-white to-gray-400 bg-clip-text text-transparent">
  Gradient text
</div>

Border Gradients

<div className="h-px w-full bg-gradient-to-r from-transparent via-[#2D2D37]/60 to-transparent" />

Typography Scale

Consistent text sizing across the app.
<h1 className="text-3xl font-bold text-ink">
  Page Title
</h1>

<h2 className="text-xl font-semibold text-white">
  Section Title
</h2>

<p className="text-sm text-ink-dull">
  Description text
</p>

<span className="text-xs text-ink-faint">
  Helper text
</span>
Scale:
  • text-xs (12px) - Helper text, labels
  • text-sm (14px) - Body text, descriptions
  • text-base (16px) - Default body
  • text-lg (18px) - Subheadings
  • text-xl (20px) - Section titles
  • text-2xl (24px) - Card titles
  • text-3xl (30px) - Page titles

Icons

Use Phosphor Icons with consistent sizing and weights.
import { Icon } from '@phosphor-icons/react';

<Icon size={16} weight="fill" />  // Buttons, small UI
<Icon size={20} weight="fill" />  // Medium UI elements
<Icon size={24} weight="fill" />  // Large icons
<Icon size={32} weight="fill" />  // Headers
<Icon size={48} weight="fill" />  // Empty states
Weight Guidelines:
  • regular - Default, inactive states
  • fill - Active states, buttons, emphasis
  • bold - Strong emphasis

Spacing Scale

Consistent spacing using Tailwind’s scale. Common patterns:
  • Card padding: p-6
  • Button padding: px-3 py-1.5 (md), px-2.5 py-1.5 (sm)
  • Section spacing: space-y-4 or space-y-6
  • Grid gaps: gap-4 or gap-6
  • Icon-text gap: gap-2 or gap-3

Accessibility

Color Contrast

All semantic colors meet WCAG AA standards:
  • text-ink on bg-app - AAA
  • text-ink-dull on bg-app-box - AA
  • text-ink-faint on bg-app-input - AA (minimum)

Focus States

Interactive elements include focus rings:
<button className="
  focus:outline-none
  focus:ring-2
  focus:ring-accent
  focus:ring-offset-2
  focus:ring-offset-app-box
">

Keyboard Navigation

All interactive primitives support keyboard navigation out of the box via Radix UI.