Roovix Design System

A production-ready component library for Roovix Research Lab. Dark-first, accessible, and built for cloud, developer, and social interfaces.

Colors

Semantic tokens that adapt to dark and light themes.

Canvas
default
--color-canvas-default
inset
--color-canvas-inset
subtle
--color-canvas-subtle
overlay
--color-canvas-overlay
elevated
--color-canvas-elevated
--color-canvas-default
--color-canvas-inset
--color-canvas-subtle
--color-canvas-overlay
--color-canvas-elevated
Foreground

Default — Primary text

Muted — Secondary descriptions

Subtle — Hints, captions, placeholders

--color-fg-default
--color-fg-muted
--color-fg-subtle
Semantic
Accent
--color-accent-*
Success
--color-success-*
Warning
--color-warning-*
Danger
--color-danger-*
Info
--color-info-*
text-accent text-success text-warning text-danger text-info
--color-accent-fg
--color-accent-emphasis
--color-accent-muted
--color-accent-subtle
--color-accent-hover
/* same for success, warning, danger, info */

Typography

Clean hierarchy with system + Inter fallback.

Scale
Heading 1
Heading 2
Heading 3
Heading 4
Body — The quick brown fox jumps over the lazy dog.
Small — The quick brown fox jumps over the lazy dog.
Caption — The quick brown fox jumps over the lazy dog.
<h1 style="font-size:clamp(1.8rem,4vw,2.5rem);font-weight:700;">Heading 1</h1>
<p style="font-size:1rem;">Body text</p>
<p style="font-size:0.875rem;color:var(--color-fg-muted);">Small</p>

Spacing & Radius

Consistent spacing scale and corner radii.

Spacing
--space-1
--space-2
--space-3
--space-4
--space-5
--space-6
--space-1: 4px;  --space-2: 8px;  --space-3: 12px;
--space-4: 16px; --space-5: 24px; --space-6: 32px;
Radius
1
--radius-1
2
--radius-2
3
--radius-3
4
--radius-4
--radius-full
--radius-1: 4px;  --radius-2: 8px;  --radius-3: 12px;
--radius-4: 16px; --radius-full: 9999px;

Shadows & Borders

Elevation and border tokens.

Shadows
xs
sm
md
lg
xl
--shadow-xs
--shadow-sm
--shadow-md
--shadow-lg
--shadow-xl
Borders
subtle
muted
default
hard
accent
--color-border-subtle
--color-border-muted
--color-border-default
--color-border-hard
--color-border-accent

Motion

Subtle transitions and animations.

Transitions
fast
base
slow
--transition-fast: 0.10s ease;
--transition-base: 0.18s ease;
--transition-slow: 0.30s ease;

Buttons

Primary, secondary, ghost, outline, and semantic variants.

Variants
<button class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-ghost">Ghost</button>
<button class="btn btn-outline">Outline</button>
<button class="btn btn-success">Success</button>
<button class="btn btn-danger">Danger</button>
Sizes & Icon
<button class="btn btn-primary btn-sm">Small</button>
<button class="btn btn-primary">Default</button>
<button class="btn btn-primary btn-lg">Large</button>
<button class="btn btn-primary btn-icon">⌕</button>
States
<button class="btn btn-primary">Default</button>
<button class="btn btn-primary" disabled>Disabled</button>
<button class="btn btn-primary">Loading <span class="spinner-button spinner-sm"></span></button>
Button Group
<div class="btn-group">
  <button class="btn btn-secondary">One</button>
  <button class="btn btn-secondary">Two</button>
  <button class="btn btn-secondary">Three</button>
</div>

Form Elements

Inputs, selects, checkboxes, radios, switches, and ranges.

Inputs
<input class="input" type="text" placeholder="Text input" />
<input class="input error" type="text" placeholder="Error" />
<textarea class="input textarea" rows="3" placeholder="Textarea"></textarea>
Input Groups
https://
$ USD
<div class="input-group">
  <span class="input-group-addon">https://</span>
  <input class="input" type="text" />
</div>
Select
Option one
Option two
Option three
<!-- Custom Select -->
<div class="select-wrap custom-select">
  <button class="select-trigger" type="button">
    <span>Option one</span>
    <span class="select-arrow"></span>
  </button>

  <div class="select-menu">
    <div class="select-option selected">Option one</div>
    <div class="select-option">Option two</div>
    <div class="select-option">Option three</div>
  </div>
</div>

<!-- JavaScript -->
<script>
document.querySelectorAll(".custom-select").forEach((select) => {
  const trigger = select.querySelector(".select-trigger");
  const options = select.querySelectorAll(".select-option");

  trigger.addEventListener("click", (event) => {
    event.stopPropagation();

    document.querySelectorAll(".custom-select.open")
      .forEach((other) => {
        if (other !== select) {
          other.classList.remove("open");
        }
      });

    select.classList.toggle("open");
  });

  options.forEach((option) => {
    option.addEventListener("click", () => {
      options.forEach((item) =>
        item.classList.remove("selected")
      );

      option.classList.add("selected");

      trigger.querySelector("span").textContent =
        option.textContent;

      select.classList.remove("open");
    });
  });
});

document.addEventListener("click", () => {
  document.querySelectorAll(".custom-select.open")
    .forEach((select) => {
      select.classList.remove("open");
    });
});
</script>
Checkbox, Radio, Switch
<label class="control"><input type="checkbox" /> Label</label>
<label class="switch"><input type="checkbox" /><span class="slider"></span></label>
Range / Slider
<input class="range" type="range" min="0" max="100" value="60" />

Alerts & Toasts

Inline alerts and toast notifications.

Inline Alerts
Info — This is an informational message.
Success — Operation completed successfully.
Warning — Please review your inputs.
Error — Something went wrong.
<div class="alert alert-success">…</div>
<div class="alert alert-danger">…</div>
Toasts
Information
Your profile has been updated.
Success
File uploaded successfully.
Warning
Low storage space remaining.
Error
Failed to connect to server.
<div class="toast toast-success">…</div>

Badges & Tags

Status indicators and metadata labels.

Badges
Active Online Pending Failed Beta Archived
<span class="badge badge-accent">Active</span>
<span class="badge badge-success">Online</span>
Tags
javascript react production deprecated v2.1.0
<span class="tag">javascript</span>
<span class="tag tag-accent">react</span>

Loading

Spinners, skeletons, and progress indicators.

Spinners
<span class="spinner"></span>
<span class="spinner spinner-sm"></span>
<span class="spinner spinner-lg"></span>
<div class="spinner-dots"><span></span><span></span><span></span></div>
Skeletons
<div class="skeleton skeleton-text" style="width:70%;"></div>
<div class="skeleton skeleton-avatar"></div>
<div class="skeleton skeleton-card">…</div>
Progress
<div class="progress"><div class="bar" style="width:45%;"></div></div>
<div class="progress indeterminate"><div class="bar"></div></div>

Tabs

Horizontal tab navigation.

Tabs
Active tab: Overview
<div class="tabs" role="tablist">
  <button class="tab active" role="tab">Overview</button>
  <button class="tab" role="tab">Metrics</button>
</div>

<!-- JavaScript -->
<script>
// ---------- Tabs interaction ----------
document.querySelectorAll('.tabs').forEach(function(tabGroup) {
  const tabs = tabGroup.querySelectorAll('.tab');
  tabs.forEach(function(tab) {
    tab.addEventListener('click', function() {
      tabs.forEach(function(t) { t.classList.remove('active'); });
      this.classList.add('active');
      const body = this.closest('.demo-body');
      if (body) {
        const desc = body.querySelector('div:last-child');
        if (desc && desc.style && desc.textContent.includes('Active tab:')) {
          desc.textContent = 'Active tab: ' + this.textContent;
        }
      }
    });
  });
});
</script>

Pagination

Page navigation controls.

Pagination
<nav class="pagination" aria-label="Pagination">
  <button class="page" disabled>‹</button>
  <button class="page active">1</button>
  <button class="page">2</button>
  <button class="page">›</button>
</nav>

Cards & Stats

Content containers and metric cards.

Cards

Roovix Compute

Running

4 vCPUs · 16 GB RAM · Ubuntu 22.04

us-west-2 · 12 min ago

Roovix Storage

Syncing

256 GB SSD · 78% used

2.4 TB available

Roovix API

Stable

v3.2.1 · 99.97% uptime

12.4M requests today
<div class="card">…</div>
Stat Cards
1,284
Total requests
↑ 12.5%
42.6ms
Avg. latency
↓ 3.2%
99.97%
Uptime
↑ 0.02%
8.4TB
Storage used
↑ 4.1%
<div class="card card-stat">
  <div class="stat-value">1,284</div>
  <div class="stat-label">Total requests</div>
</div>

Tables

Data tables with clean styling.

Table
InstanceStatusCPUMemoryRegion
compute-01Running42%6.2/16 GBus-west-2
compute-02Stopping12%2.1/16 GBus-east-1
compute-03Failed0%0/16 GBeu-central-1
compute-04Provisioningap-south-1
<div class="table-wrap">
  <table>…</table>
</div>

Avatars

User avatars and groups.

Avatars
A
B
C
<div class="avatar">A</div>
<div class="avatar avatar-lg">B</div>
<div class="avatar avatar-sm">C</div>
Avatar Group
A
B
C
D
+3
<div class="avatar-group">
  <div class="avatar">A</div>
  <div class="avatar">B</div>
</div>

Tooltip

Hover-triggered context tips.

Tooltip
I'm a tooltip
Beta This feature is in preview
<div class="tooltip-trigger">
  <button class="btn">Hover me</button>
  <span class="tooltip">Tooltip text</span>
</div>

File Upload

Drop zones, file items, upload progress, and real drag-and-drop management.

Drop Zone — Single & Multiple
Drop files here or click to browse
Supports images, documents, archives — up to 50MB per file
0 files
No files added yet. Drag & drop or click the zone above.
<!-- HTML -->
<div class="drop-zone" id="dropZone">
  <div class="dz-icon">⬇</div>
  <div class="dz-text">Drop files here or click to browse</div>
  <div class="dz-hint">Supports images, documents, archives — up to 50MB per file</div>
  <input type="file" id="fileInput" multiple style="display: none;" />
</div>

<button class="btn btn-primary btn-sm" id="uploadAllBtn">⬆ Upload All</button>
<button class="btn btn-secondary btn-sm" id="clearAllBtn">✕ Clear All</button>

<div id="fileList"></div>

<!-- JavaScript -->
<script>
(function() {
  'use strict';

  // ---------- DOM refs ----------
  const dropZone = document.getElementById('dropZone');
  const fileInput = document.getElementById('fileInput');
  const fileList = document.getElementById('fileList');
  const emptyMessage = document.getElementById('emptyMessage');
  const fileCount = document.getElementById('fileCount');
  const uploadAllBtn = document.getElementById('uploadAllBtn');
  const clearAllBtn = document.getElementById('clearAllBtn');
  const uploadStatus = document.getElementById('uploadStatus');

  // ---------- State ----------
  let files = []; // Array of { file: File, id: string, status: 'idle'|'uploading'|'done'|'error', progress: 0 }

  // ---------- Helpers ----------
  function formatSize(bytes) {
    if (bytes === 0) return '0 B';
    const k = 1024;
    const sizes = ['B', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    const val = (bytes / Math.pow(k, i)).toFixed(1);
    return val + ' ' + sizes[i];
  }

  function getFileIcon(file) {
    const type = file.type;
    if (type.startsWith('image/')) return '🖼';
    if (type.startsWith('video/')) return '🎬';
    if (type.startsWith('audio/')) return '🎵';
    if (type.includes('pdf')) return '📄';
    if (type.includes('zip') || type.includes('tar') || type.includes('gzip')) return '📦';
    if (type.includes('javascript') || type.includes('json') || type.includes('yaml') || type.includes('xml')) return '📝';
    return '📎';
  }

  function generateId() {
    return Date.now().toString(36) + Math.random().toString(36).substr(2, 6);
  }

  // ---------- Render file list ----------
  function renderFiles() {
    // Clear list
    fileList.innerHTML = '';

    if (files.length === 0) {
      const msg = document.createElement('div');
      msg.id = 'emptyMessage';
      msg.style.cssText =
        'text-align:center;color:var(--color-fg-subtle);font-size:0.85rem;padding:var(--space-3);';
      msg.textContent = 'No files added yet. Drag & drop or click the zone above.';
      fileList.appendChild(msg);
      fileCount.textContent = '0 files';
      return;
    }

    files.forEach(function(f) {
      const item = document.createElement('div');
      item.className = 'file-item';
      item.dataset.id = f.id;

      const icon = document.createElement('span');
      icon.className = 'file-icon';
      icon.textContent = getFileIcon(f.file);

      const info = document.createElement('div');
      info.className = 'file-info';

      const name = document.createElement('div');
      name.className = 'file-name';
      name.textContent = f.file.name;

      const size = document.createElement('div');
      size.className = 'file-size';
      size.textContent = formatSize(f.file.size);

      info.appendChild(name);
      info.appendChild(size);

      // Progress bar if uploading
      if (f.status === 'uploading') {
        const progWrap = document.createElement('div');
        progWrap.className = 'progress';
        progWrap.style.cssText = 'width:100%;height:3px;margin-top:4px;';
        const bar = document.createElement('div');
        bar.className = 'bar';
        bar.style.width = f.progress + '%';
        progWrap.appendChild(bar);
        info.appendChild(progWrap);
      }

      const statusEl = document.createElement('span');
      statusEl.className = 'file-status';
      if (f.status === 'idle') {
        statusEl.textContent = '⏳ Pending';
        statusEl.style.color = 'var(--color-fg-muted)';
      } else if (f.status === 'uploading') {
        statusEl.textContent = '⟳ ' + Math.round(f.progress) + '%';
        statusEl.className = 'file-status uploading';
      } else if (f.status === 'done') {
        statusEl.textContent = '✓ Done';
        statusEl.className = 'file-status success';
      } else if (f.status === 'error') {
        statusEl.textContent = '✕ Failed';
        statusEl.className = 'file-status error';
      }

      // Remove button
      const removeBtn = document.createElement('button');
      removeBtn.className = 'btn btn-ghost btn-sm';
      removeBtn.style.cssText = 'padding:0.1rem 0.4rem;font-size:0.7rem;color:var(--color-fg-subtle);';
      removeBtn.textContent = '✕';
      removeBtn.title = 'Remove file';
      removeBtn.addEventListener('click', function(e) {
        e.stopPropagation();
        removeFile(f.id);
      });
      removeBtn.addEventListener('mouseenter', function() {
        this.style.color = 'var(--color-danger-fg)';
      });
      removeBtn.addEventListener('mouseleave', function() {
        this.style.color = 'var(--color-fg-subtle)';
      });

      item.appendChild(icon);
      item.appendChild(info);
      item.appendChild(statusEl);
      item.appendChild(removeBtn);

      fileList.appendChild(item);
    });

    fileCount.textContent = files.length + ' file' + (files.length > 1 ? 's' : '');
  }

  // ---------- Add files ----------
  function addFiles(fileListFromInput) {
    let added = 0;
    for (let i = 0; i < fileListFromInput.length; i++) {
      const file = fileListFromInput[i];
      // Skip duplicates by name + size
      const exists = files.some(function(f) {
        return f.file.name === file.name && f.file.size === file.size;
      });
      if (!exists) {
        files.push({
          file: file,
          id: generateId(),
          status: 'idle',
          progress: 0
        });
        added++;
      }
    }
    if (added > 0) {
      renderFiles();
      // Show status briefly
      uploadStatus.style.display = 'block';
      uploadStatus.textContent = '✅ Added ' + added + ' file' + (added > 1 ? 's' : '');
      uploadStatus.style.background = 'var(--color-success-subtle)';
      uploadStatus.style.borderColor = 'var(--color-success-muted)';
      uploadStatus.style.color = 'var(--color-success-fg)';
      setTimeout(function() {
        uploadStatus.style.display = 'none';
      }, 2000);
    } else {
      // Duplicate
      uploadStatus.style.display = 'block';
      uploadStatus.textContent = '⚠️ All files already added';
      uploadStatus.style.background = 'var(--color-warning-subtle)';
      uploadStatus.style.borderColor = 'var(--color-warning-muted)';
      uploadStatus.style.color = 'var(--color-warning-fg)';
      setTimeout(function() {
        uploadStatus.style.display = 'none';
      }, 2000);
    }
    // Reset input
    fileInput.value = '';
  }

  // ---------- Remove file ----------
  function removeFile(id) {
    files = files.filter(function(f) { return f.id !== id; });
    renderFiles();
  }

  // ---------- Clear all ----------
  function clearAll() {
    if (files.length === 0) return;
    const anyUploading = files.some(function(f) { return f.status === 'uploading'; });
    if (anyUploading) {
      if (!confirm('Some files are uploading. Clear anyway?')) return;
    }
    files = [];
    renderFiles();
    uploadStatus.style.display = 'block';
    uploadStatus.textContent = '🗑️ All files cleared';
    uploadStatus.style.background = 'var(--color-accent-subtle)';
    uploadStatus.style.borderColor = 'var(--color-accent-muted)';
    uploadStatus.style.color = 'var(--color-accent-fg)';
    setTimeout(function() {
      uploadStatus.style.display = 'none';
    }, 1500);
  }

  // ---------- Simulate upload ----------
  function simulateUpload(fileId) {
    const f = files.find(function(item) { return item.id === fileId; });
    if (!f) return;

    f.status = 'uploading';
    f.progress = 0;
    renderFiles();

    let progress = 0;
    const interval = setInterval(function() {
      progress += Math.random() * 12 + 2;
      if (progress >= 100) {
        progress = 100;
        f.progress = 100;
        f.status = 'done';
        renderFiles();
        clearInterval(interval);
        // Update status toast
        uploadStatus.style.display = 'block';
        uploadStatus.textContent = '✅ ' + f.file.name + ' uploaded successfully';
        uploadStatus.style.background = 'var(--color-success-subtle)';
        uploadStatus.style.borderColor = 'var(--color-success-muted)';
        uploadStatus.style.color = 'var(--color-success-fg)';
        setTimeout(function() {
          uploadStatus.style.display = 'none';
        }, 2500);
        return;
      }
      f.progress = Math.min(progress, 99);
      renderFiles();
    }, 150 + Math.random() * 200);
  }

  // ---------- Upload all ----------
  function uploadAll() {
    const idleFiles = files.filter(function(f) { return f.status === 'idle'; });
    if (idleFiles.length === 0) {
      uploadStatus.style.display = 'block';
      uploadStatus.textContent = 'ℹ️ No pending files to upload';
      uploadStatus.style.background = 'var(--color-accent-subtle)';
      uploadStatus.style.borderColor = 'var(--color-accent-muted)';
      uploadStatus.style.color = 'var(--color-accent-fg)';
      setTimeout(function() {
        uploadStatus.style.display = 'none';
      }, 2000);
      return;
    }

    uploadStatus.style.display = 'block';
    uploadStatus.textContent = '⬆ Uploading ' + idleFiles.length + ' file' + (idleFiles.length > 1 ? 's' : '') +
      '…';
    uploadStatus.style.background = 'var(--color-accent-subtle)';
    uploadStatus.style.borderColor = 'var(--color-accent-muted)';
    uploadStatus.style.color = 'var(--color-accent-fg)';

    idleFiles.forEach(function(f, index) {
      // Stagger uploads
      setTimeout(function() {
        simulateUpload(f.id);
      }, index * 400);
    });

    // Update status after all started
    setTimeout(function() {
      uploadStatus.textContent = '⬆ Uploading ' + idleFiles.length + ' files…';
    }, 300);
  }

  // ---------- Event listeners ----------

  // Click drop zone → open file picker
  dropZone.addEventListener('click', function(e) {
    if (e.target.tagName !== 'INPUT') {
      fileInput.click();
    }
  });

  // File input change
  fileInput.addEventListener('change', function() {
    if (this.files.length > 0) {
      addFiles(this.files);
    }
  });

  // Drag and drop events
  dropZone.addEventListener('dragover', function(e) {
    e.preventDefault();
    this.classList.add('dragover');
  });

  dropZone.addEventListener('dragleave', function(e) {
    e.preventDefault();
    this.classList.remove('dragover');
  });

  dropZone.addEventListener('drop', function(e) {
    e.preventDefault();
    this.classList.remove('dragover');
    const dt = e.dataTransfer;
    if (dt.files && dt.files.length > 0) {
      addFiles(dt.files);
    }
  });

  // Upload all button
  uploadAllBtn.addEventListener('click', uploadAll);

  // Clear all button
  clearAllBtn.addEventListener('click', clearAll);

  // ---------- Expose for copy-code snippets ----------
  window.fileUploadDemo = {
    addFiles: addFiles,
    removeFile: removeFile,
    clearAll: clearAll,
    uploadAll: uploadAll,
    files: files,
    renderFiles: renderFiles
  };

  // ---------- Initial render ----------
  renderFiles();

  // Log to console for debugging
  console.log('✅ File upload demo ready — use window.fileUploadDemo to control it.');
  console.log('📂 Supported: drag & drop, multiple files, progress simulation.');

})();
</script>
File Item States
📄
deployment-spec.yaml
2.4 KB
✓ Done
🖼
architecture-diagram.png
1.8 MB
⟳ 64%
📦
roovix-cli-v2.1.0.tar.gz
12.6 MB
✕ Failed
<!-- File item — success -->
<div class="file-item">
  <span class="file-icon">📄</span>
  <div class="file-info">
    <div class="file-name">deployment-spec.yaml</div>
    <div class="file-size">2.4 KB</div>
  </div>
  <span class="file-status success">✓ Done</span>
</div>

<!-- File item — uploading with progress -->
<div class="file-item">
  <span class="file-icon">🖼</span>
  <div class="file-info">
    <div class="file-name">image.png</div>
    <div class="file-size">1.8 MB</div>
    <div class="progress" style="width: 100%; height: 3px; margin-top: 4px;">
      <div class="bar" style="width: 64%;"></div>
    </div>
  </div>
  <span class="file-status uploading">⟳ 64%</span>
</div>

<!-- File item — error -->
<div class="file-item">
  <span class="file-icon">📦</span>
  <div class="file-info">
    <div class="file-name">archive.tar.gz</div>
    <div class="file-size">12.6 MB</div>
  </div>
  <span class="file-status error">✕ Failed</span>
</div>

Post & Comment

Social feed components.

Post
R
Roovix Research
2 hours ago · 🌍 Public
We're excited to announce the general availability of Roovix Compute v3. New features include GPU acceleration, automated scaling, and a 40% reduction in latency.
❤️ 142 💬 28 ↗ 64
<div class="card">
  <div style="display:flex;gap:var(--space-3);">…</div>
  <div>Post content…</div>
  <div style="display:flex;gap:var(--space-4);">…</div>
</div>
Comment
A
alex 3 min ago
This is a great update! When will the new API docs be published?
❤️ 4 Reply
<div style="display:flex;gap:var(--space-3);padding:var(--space-3);background:var(--color-canvas-inset);border:1px solid var(--color-border-subtle);border-radius:var(--radius-2);">…</div>

Profile

User profile header and card.

Profile Header
R
Roovix Labs
@roovix · Research & Engineering
📍 San Francisco 🔗 roovix.dev 📅 Joined 2024
<div style="display:flex;gap:var(--space-4);align-items:center;padding:var(--space-4);border:1px solid var(--color-border-subtle);border-radius:var(--radius-3);">…</div>