DEV Community

Cover image for ai-ui.fscss: shell, messages, prompt, and stream — without a UI kit runtime

ai-ui.fscss: shell, messages, prompt, and stream — without a UI kit runtime

ai-ui.fscss is a presentation layer for AI product surfaces in the FSCSS ecosystem: tokens, layout, and chrome only. No models, no streaming clients, no backend.

Your app still owns data, events, and model I/O. This module owns how the shell, messages, prompt bar, and related pieces look and sit on the page — and it compiles to plain CSS.


What it covers

Surface Role
Shell App grid: sidebar + main + room for the prompt
Messages User / assistant rows, bubbles, avatars
Prompt Textarea + send control
Stream Caret on partial assistant text
Thinking Processing dots
Citations Source chips under answers
Artifacts Code / JSON / doc cards
Workflow Step nodes and edges
Usage Token and metric cards

Same contract as other FSCSS modules: import registers @define helpers, you call them with host selectors, markup uses those class names, design tokens flow through CSS variables.


Install mindset

You need FSCSS 1.2.6+ (CLI, browser runtime, IDE, or the Python wrapper - same language).

Registry-style import:

@import((*) from ai-ui)
Enter fullscreen mode Exit fullscreen mode

Or selective, when you only want part of the surface:

@import((
  ai-root,
  ai-theme-research,
  ai-shell,
  ai-messages,
  ai-message,
  ai-message-assistant,
  ai-bubble,
  ai-citations,
  ai-citation,
  ai-stream
) from ai-ui)
Enter fullscreen mode Exit fullscreen mode

In the browser IDE, package automation can scaffold demos with something like libon ai-ui / libck ai-ui to enable the pack in your workspace.


Tokens and themes

Start from a root once:

@ai-root()                 /* :root */
/* or @ai-root(root.my-scope) for a scoped tree */
Enter fullscreen mode Exit fullscreen mode

Themes override token values on that root:

Helper Character
@ai-theme-minimal() Quiet, near-monochrome
@ai-theme-copilot() Blues / greens, product-chat feel
@ai-theme-research() Slate + cyan, citation-heavy
@ai-theme-support() Calm support-desk green
@ai-theme-dashboard() Stronger accent for ops / agent boards

Composition presets apply root + theme in one call:

@ai-compose-copilot()
/* same idea as @ai-root() + @ai-theme-copilot() */
Enter fullscreen mode Exit fullscreen mode

Also available: @ai-compose-minimal(), @ai-compose-research(), @ai-compose-support(), @ai-compose-dashboard().

You still call shell / message / prompt helpers for structure. Presets only set the token layer.

Typical tokens you will override locally:

  • Surfaces: --ai-bg, --ai-surface, --ai-card
  • Brand: --ai-accent, --ai-accent-2
  • Roles: --ai-user, --ai-assistant
  • Type: --ai-text, --ai-text-muted
  • Layout: --ai-sidebar-w, --ai-prompt-h, --ai-msg-max
.ai-shell {
  --ai-accent: #a78bfa;
  --ai-sidebar-w: 240px;
}
Enter fullscreen mode Exit fullscreen mode

Minimal copilot shell

Structure helpers take the host selector:

@import((*) from ai-ui)

@ai-compose-copilot()

@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)
Enter fullscreen mode Exit fullscreen mode

Markup shape:

<div class="ai-shell">
  <aside class="ai-sidebar">/* threads, nav */</aside>
  <main class="ai-main">
    <div class="ai-messages">
      <div class="ai-message ai-message-user">
        <div class="ai-avatar">U</div>
        <div class="ai-bubble">Track order 12389</div>
      </div>
      <div class="ai-message ai-message-assistant">
        <div class="ai-avatar">AI</div>
        <div class="ai-bubble">Out for delivery…</div>
      </div>
    </div>
    <form class="ai-prompt" onsubmit="return false">
      <textarea class="ai-prompt-box" placeholder="Ask…" rows="1"></textarea>
      <button class="ai-prompt-send" type="submit" aria-label="Send">↑</button>
    </form>
  </main>
</div>
Enter fullscreen mode Exit fullscreen mode

User rows align one way; assistant rows the other. Bubbles and avatars are separate pieces so you can swap markup without fighting the layout grid.


Stream, thinking, citations, artifacts

When the answer is still arriving:

@ai-stream(.ai-stream)
@ai-thinking(.ai-thinking)
Enter fullscreen mode Exit fullscreen mode
<div class="ai-bubble ai-stream">Partial answer…</div>
<div class="ai-thinking">
  <span class="ai-dot"></span>
  <span class="ai-dot"></span>
  <span class="ai-dot"></span>
  Thinking…
</div>
Enter fullscreen mode Exit fullscreen mode

Citations under an answer:

@ai-citations(.ai-citations)
@ai-citation(.ai-citation)
Enter fullscreen mode Exit fullscreen mode
<div class="ai-citations">
  <a class="ai-citation" href="#"><span class="ai-cite-num">1</span> source.api</a>
</div>
Enter fullscreen mode Exit fullscreen mode

Artifact card (code / JSON / doc):

@ai-artifacts(.ai-artifacts)
@ai-artifact-header(.ai-artifact-header)
@ai-artifact-body(.ai-artifact-body)
Enter fullscreen mode Exit fullscreen mode
<div class="ai-artifacts">
  <div class="ai-artifact-header">
    <span>status.json</span>
    <span>JSON</span>
  </div>
  <div class="ai-artifact-body">{ /* … */ }</div>
</div>
Enter fullscreen mode Exit fullscreen mode

Workflow strip for agent steps:

@ai-workflow(.ai-workflow)
@ai-node(.ai-node)
@ai-edge(.ai-edge)
Enter fullscreen mode Exit fullscreen mode

Usage / stats for an ops board:

@ai-usage(.ai-usage)
@ai-stat(.ai-stat)
Enter fullscreen mode Exit fullscreen mode
<div class="ai-usage">
  <div class="ai-stat">
    <span class="ai-stat-label">Tokens today</span>
    <span class="ai-stat-value">48.2k</span>
    <span class="ai-stat-delta up">+12%</span>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Tool chips (search / generate / etc.):

@ai-tools(.ai-tools)
@ai-tool-chip(.ai-tool-chip)
Enter fullscreen mode Exit fullscreen mode

Utilities when you need a plain card or divider in the same token system: @ai-card, @ai-divider, @ai-scroll-fade.


Compose with others

ai-ui does not draw charts. For an ops dashboard you combine modules:

  • ai-ui — shell, message column, prompt, workflow, usage chrome
  • st-core@v2 — pure CSS charts from arrays and custom properties
  • circle-progress — rings for quotas or completion
  • overlapui — avatar stacks in a sidebar or header

Import both (or all) and keep selectors in their own namespaces. Example depth pattern:

@import((*) from ai-ui)
@import((*) from st-core@v2)

@ai-compose-dashboard()
@st-root()
/* chart fill / line helpers on a card inside .ai-main */
Enter fullscreen mode Exit fullscreen mode

That is the same idea as a product screen: one token story for the AI chrome, another for the chart series, no shared class collisions by design.


What this is not

  • Not a chat backend
  • Not a streaming protocol
  • Not a replacement for accessibility work in your markup (labels, live regions, focus order stay your job)

It is the CSS layer so you are not rewriting the same shell for every AI experiment.


Mental model

  1. Register — @import from ai-ui
  2. Theme — @ai-compose-* or @ai-root + @ai-theme-*
  3. Structure — @ai-shell, @ai-messages, @ai-prompt, … with your selectors
  4. Markup — class names that match those selectors
  5. Ship — compile with CLI / IDE / Python wrapper, or prototype with the browser runtime

Selective imports keep the emitted CSS small when you only need research citations or a dashboard strip.


If you are building copilots, research assistants, support bots, or agent ops boards and you already like FSCSS modules, ai-ui.fscss is the matching piece for the conversation chrome: detailed enough to look intentional, light enough that the framework and the model stay outside the stylesheet.

Source and full helper list live in the ai-ui.fscss repo
try a compose preset first, then add stream, citations, and artifacts only where the product actually needs them.

Top comments (0)