If you have ever built an AI product UI, you know the pattern. A sidebar, a chat column, a metrics panel, a streaming caret, some usage cards. It is a lot of CSS before you write a single line of product logic.

In this tutorial we build an AI Ops Console using four FSCSS modules, compile everything to one plain CSS file, and wire it up with a small vanilla JavaScript layer. No frameworks. No runtime preprocessor in the browser.

CodePen preview

What we are building

An AI Ops Console with a desktop layout (sidebar, chat, metrics panel) and a mobile layout (hamburger sidebar drawer, bottom sheet metrics panel at 960px and below).

Surface Module
App shell, sidebar, messages, prompt, stream caret, thinking dots, usage cards, workflow nodes, tool chips, citations ai-ui.fscss
Token burn area and line chart (clip-path, no SVG or canvas) st-core@v2
GPU, Queue and Quota rings circle-progress
Team avatar stack overlapui
Mock agent replies, live chart nudges, mobile drawers dashboard.js

The project is split into four files:

ai-dashboard/ ├── dashboard.html # markup only ├── dashboard.fscss # all FSCSS + layout ├── dashboard.css # compiled output (generated) └── dashboard.js # chat, drawers, chart updates 

You compile once and ship plain CSS.

Prerequisites

  • Node.js 18 or newer (for the FSCSS CLI)
  • A modern browser
  • Optional: VS Code with the FSCSS Support extension

Install the compiler globally:

npm install -g fscss@1.2.6 

You need FSCSS 1.2.6 or newer. ai-ui requires it, and st-core needs 1.2.3 or newer.

1. Project setup

mkdir ai-dashboard && cd ai-dashboard touch dashboard.html dashboard.fscss dashboard.js 

dashboard.css will be generated by the CLI in step 3.

2. dashboard.fscss: the styles source

This file imports the four modules, calls their helpers against your own class names, defines the chart series, and holds all layout and responsive rules.

/* dashboard.fscss AI Ops Dashboard: FSCSS source Compile: fscss dashboard.fscss dashboard.css */ /* Modules (registry names; CLI + runtime resolve these) */ @import((*) from ai-ui) @import((*) from st-core@v2) @import((*) from circle-progress) @import((*) from overlapui) /* Theme + design tokens */ @ai-compose-dashboard() /* ai-ui tokens + dashboard theme */ @st-root() /* st-core chart tokens */ @progress-root() /* circle-progress tokens */ @ou-root() /* overlapui tokens */ /* ai-ui structure helpers */ @ai-shell(.ai-shell) @ai-sidebar(.ai-sidebar) @ai-main(.ai-main) @ai-messages(.ai-messages) @ai-message(.ai-message) @ai-message-user(.ai-message-user) @ai-message-assistant(.ai-message-assistant) @ai-bubble(.ai-bubble) @ai-avatar(.ai-avatar) @ai-prompt(.ai-prompt) @ai-prompt-box(.ai-prompt-box) @ai-prompt-send(.ai-prompt-send) @ai-stream(.ai-stream) @ai-thinking(.ai-thinking) @ai-usage(.ai-usage) @ai-stat(.ai-stat) @ai-workflow(.ai-workflow) @ai-node(.ai-node) @ai-edge(.ai-edge) @ai-tools(.ai-tools) @ai-tool-chip(.ai-tool-chip) @ai-card(.ai-card) @ai-divider(.ai-divider) @ai-citations(.ai-citations) @ai-citation(.ai-citation) /* st-core linear chart */ @arr tokenSeries[32, 48, 55, 42, 68, 75, 61, 88, 72, 91, 84, 96] @st-chart-fill(.chart-fill, tokenSeries) @st-chart-line(.chart-line, tokenSeries) .token-chart { @st-chart-points(tokenSeries) position: relative; height: 160px; width: 100%; background: var(--st-surface, #161422); border-radius: 12px; overflow: hidden; } /* circle-progress */ @circle-progress(.cp) .cp-72 { @progress-range(72) } .cp-48 { @progress-range(48) } .cp-91 { @progress-range(91) } .cp { --progress-size: 110px; --progress-stroke: 10px; --progress-color-bg: transparent; --progress-color-track: rgba(255, 255, 255, 0.08); --progress-color-arc: var(--ai-accent, #e879f9); --progress-color-glow: color-mix(in srgb, var(--ai-accent, #e879f9) 40%, transparent); --progress-color-value: var(--ai-text, #f5f3ff); --progress-color-label: var(--ai-text-muted, #a78bfa); font-size: 0.95rem; font-weight: 600; } /* overlapui avatars */ @avatar-overlap(.team-faces) .team-faces { --ou-avatar-size: 36px; --ou-overlap: -12px; margin-block: 8px; } /* Base layout */ * { box-sizing: border-box; margin: 0; padding: 0; } html, body { height: 100%; font-family: var(--ai-font, system-ui, -apple-system, sans-serif); background: var(--ai-bg, #0c0a12); color: var(--ai-text, #f5f3ff); -webkit-text-size-adjust: 100%; } .ai-shell { min-height: 100vh; min-height: 100dvh; height: 100vh; height: 100dvh; display: grid; grid-template-columns: var(--ai-sidebar-w, 240px) 1fr; } .ai-sidebar { display: flex; flex-direction: column; gap: 16px; padding: 20px 16px; overflow-y: auto; border-right: 1px solid var(--ai-border, rgba(255, 255, 255, 0.08)); background: var(--ai-bg, #0c0a12); z-index: 40; } .brand { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 1.05rem; letter-spacing: -0.02em; } .brand-mark { width: 28px; height: 28px; border-radius: 8px; background: linear-gradient(135deg, var(--ai-accent, #e879f9), #818cf8); display: grid; place-items: center; font-size: 0.75rem; font-weight: 800; color: #0c0a12; flex-shrink: 0; } .nav { display: flex; flex-direction: column; gap: 4px; } .nav a { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: 8px; color: var(--ai-text-muted, #a78bfa); text-decoration: none; font-size: 0.875rem; transition: background 0.15s, color 0.15s; } .nav a:hover, .nav a.active { background: color-mix(in srgb, var(--ai-accent, #e879f9) 12%, transparent); color: var(--ai-text, #f5f3ff); } .side-section { display: flex; flex-direction: column; gap: 8px; } .side-section h3 { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ai-text-muted, #a78bfa); font-weight: 600; } .cp-row { display: flex; justify-content: space-between; gap: 8px; flex-wrap: wrap; } .cp-item { display: flex; flex-direction: column; align-items: center; gap: 4px; font-size: 0.7rem; color: var(--ai-text-muted, #a78bfa); } .ai-main { display: flex; flex-direction: column; min-height: 0; min-width: 0; overflow: hidden; } .topbar { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--ai-border, rgba(255, 255, 255, 0.08)); gap: 10px; flex-wrap: wrap; flex-shrink: 0; } .topbar h1 { font-size: 1rem; font-weight: 600; } .topbar-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; } .chip { font-size: 0.75rem; padding: 4px 10px; border-radius: 999px; background: color-mix(in srgb, var(--ai-accent, #e879f9) 15%, transparent); color: var(--ai-accent, #e879f9); border: 1px solid color-mix(in srgb, var(--ai-accent, #e879f9) 30%, transparent); white-space: nowrap; } .workspace { flex: 1; display: grid; grid-template-columns: 1fr minmax(260px, 320px); min-height: 0; overflow: hidden; } .chat-col { display: flex; flex-direction: column; min-height: 0; min-width: 0; border-right: 1px solid var(--ai-border, rgba(255, 255, 255, 0.06)); } .ai-messages { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 14px; -webkit-overflow-scrolling: touch; } .ai-prompt { padding: 12px 16px calc(16px + env(safe-area-inset-bottom, 0px)); border-top: 1px solid var(--ai-border, rgba(255, 255, 255, 0.06)); flex-shrink: 0; } .right-panel { display: flex; flex-direction: column; gap: 16px; padding: 16px; overflow-y: auto; background: color-mix(in srgb, var(--ai-surface, #12101a) 80%, transparent); -webkit-overflow-scrolling: touch; } .panel-card { background: var(--ai-card, #1a1625); border: 1px solid var(--ai-border, rgba(255, 255, 255, 0.08)); border-radius: 12px; padding: 14px; } .panel-card h3 { font-size: 0.8rem; font-weight: 600; margin-bottom: 10px; color: var(--ai-text-muted, #a78bfa); } .ai-workflow { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 0.75rem; } .ai-node { padding: 6px 10px; border-radius: 6px; background: var(--ai-surface, #161422); border: 1px solid var(--ai-border, rgba(255, 255, 255, 0.1)); } .ai-node.is-active { border-color: var(--ai-accent, #e879f9); color: var(--ai-accent, #e879f9); box-shadow: 0 0 0 1px color-mix(in srgb, var(--ai-accent, #e879f9) 40%, transparent); } .ai-edge { width: 16px; height: 2px; background: var(--ai-border, rgba(255, 255, 255, 0.2)); } .ai-usage { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } .ai-stat { background: var(--ai-surface, #161422); border-radius: 10px; padding: 10px 12px; } .ai-stat-label { display: block; font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ai-text-muted, #a78bfa); margin-bottom: 4px; } .ai-stat-value { font-size: 1.15rem; font-weight: 700; letter-spacing: -0.02em; } .ai-stat-delta { font-size: 0.7rem; margin-left: 6px; } .ai-stat-delta.up { color: #4fffb0; } .ai-stat-delta.down { color: #ff5e7d; } .ai-tools { display: flex; flex-wrap: wrap; gap: 6px; } .ai-tool-chip { font-size: 0.7rem; padding: 4px 8px; border-radius: 6px; background: var(--ai-surface, #161422); border: 1px solid var(--ai-border, rgba(255, 255, 255, 0.1)); } .ai-tool-chip.is-done { border-color: #4fffb0; color: #4fffb0; } .ai-tool-chip.is-running { border-color: var(--ai-accent, #e879f9); color: var(--ai-accent, #e879f9); } .muted { color: var(--ai-text-muted, #a78bfa); font-size: 0.8rem; } /* Mobile controls (hidden on desktop) */ .menu-btn, .panel-btn, .sidebar-backdrop { display: none; } /* TABLET <= 960px */ @media (max-width: 960px) { .ai-shell { grid-template-columns: 1fr; } .ai-sidebar { position: fixed; inset: 0 auto 0 0; width: min(280px, 86vw); transform: translateX(-105%); transition: transform 0.25s ease; box-shadow: none; border-right: 1px solid var(--ai-border, rgba(255, 255, 255, 0.08)); } body.sidebar-open .ai-sidebar { transform: translateX(0); box-shadow: 8px 0 32px rgba(0, 0, 0, 0.45); } .sidebar-backdrop { display: block; position: Source:https://dev.to/fscss/build-an-ai-ops-dashboard-with-fscss-ai-ui-st-core-circle-progress-overlapui-59m5

Related posts