name: dips
description: |
Use this whenever a response could benefit from richer visualization, guided
interaction, or a touch of fun — pickers, calculators, sliders, mini
explorers, charts, animated demos, choose-your-own-adventure prompts,
anything that lands better as a hydrating widget than as plain prose. Dips
are ephemeral shtml code blocks rendered inline in chat (no state
persistence, lick-only). Reach for them generously: every interactive moment
the user gets is a moment they don't have to type a clarifying message. For
persistent dashboards, editors, or multi-page apps use sprinkles instead.
allowed-tools: bash
Dips
Dips are inline shtml code blocks in chat that hydrate into sandboxed interactive widgets. Ephemeral — no state persistence, no readFile. Only slicc.lick(event) is available for agent communication; always pack payloads as { action, data: { ... } } for a predictable shape on the cone side.
Use them generously. A dip is the right answer any time a response could benefit from visualization, interaction, or a moment of delight. Don't reserve them for "complex" tasks — a slider that lets the user feel a number, a chart that beats a paragraph of stats, a button that's faster than typing "yes" all earn their keep.
| Reach for a dip when … | Reach for a sprinkle when … |
|---|---|
| The answer would land better as a widget than as prose | The user needs a persistent dashboard / editor / multi-page app |
| The user is choosing, confirming, or tuning a value | The UI must survive across turns or sessions |
| You want a chart, animation, or interactive demo | You need readFile, screenshot, or any persistent state |
| The result deserves a bit of fun | The interaction is long-running |
For a gallery of 10 ready-to-adapt patterns (drag-on-canvas, slider→DOM reflow, paste→tree, ...) read the companion file: read_file /workspace/skills/dips/patterns.md.
Card structure
Always wrap interactive content in .sprinkle-action-card for visual containment. Don't output bare HTML — cards need a visible boundary.
<div class="sprinkle-action-card">
<div class="sprinkle-action-card__header">Title <span class="sprinkle-badge sprinkle-badge--notice">Status</span></div>
<div class="sprinkle-action-card__body">Description text</div>
<div class="sprinkle-action-card__actions">
<button class="sprinkle-btn sprinkle-btn--secondary" onclick="slicc.lick('cancel')">Cancel</button>
<button class="sprinkle-btn sprinkle-btn--primary" onclick="slicc.lick('confirm')">Confirm</button>
</div>
</div>
When the user clicks a button, you receive the lick as a message. Respond conversationally, with another dip, or by spawning a scoop.
Use existing components inside cards
- Progress →
.sprinkle-progress-barwith--progressCSS variable. Not raw colored divs. - Status indicators →
.sprinkle-status-lightwith--positive/--negative/--noticevariants. - Stats →
.sprinkle-stat-cardgrid. - Tables →
.sprinkle-tablewith.sprinkle-badgefor severity. - Badges →
.sprinkle-badgevariants (--positive,--negative,--notice,--informative).
Multiple cards in one message: each is a separate .sprinkle-action-card. The iframe padding handles spacing. Don't wrap multiple cards in a single container.
Pre-styled form elements
Inside dips, bare HTML form elements are pre-styled to match S2:
<input type="range">— 4px track, 18px accent thumb.<input type="text">,<textarea>— S2 layer-2 background, accent focus ring.rowsis fine as an initial size; never addoverflow:autoor a fixed pixel height — the dip auto-sizes (see "Sizing").<select>— S2 styled with focus ring.<button>— pill-rounded, 28px height, hover state. Use.sprinkle-btn--primaryclass for the accent fill.<canvas>— full-width, rounded corners.<mark>— accent-tinted highlight.
No custom CSS needed for basic widgets. Just write bare HTML.
Sizing
The dip iframe auto-sizes to its content via ResizeObserver on document.body. Your content's natural height becomes the iframe height. Don't fight it.
Never set height, max-height, min-height, overflow: auto, or overflow: scroll on dip containers. The outer iframe has overflow: hidden, so any internal scroller or fixed height clips content instead of growing the iframe. Let content flow and grow.
The one exception is <canvas>: canvases need explicit pixel width/height for the drawing buffer. That's a drawing surface, not a scrollable container — set the pixel dimensions you need.
If content is long enough that it would want to scroll, it belongs in a sprinkle.
Light & dark mode
Dips inherit the parent's theme automatically. The parent injects its S2 tokens and toggles a .theme-light class on the iframe's <html> whenever the user flips themes; every var(--s2-*) token swaps in lockstep. Never hard-code dark colors — always use S2 tokens, or light-dark() for one-off custom colors (set color-scheme: light dark on an ancestor). Do not use @media (prefers-color-scheme: ...) — the parent toggle is class-based, so media queries desync. See /workspace/skills/sprinkles/style-guide.md for the full S2 token reference and a light-dark() example.
Color palette for charts
Use these classes on chart elements, diagram nodes, or badges. Two or three colors per visualization, not six. Assign by meaning, not by sequence.
| Class | Use for |
|---|---|
.c-purple |
Primary category, AI/ML |
.c-teal |
Success, growth |
.c-coral |
Secondary category |
.c-pink |
Tertiary category |
.c-gray |
Neutral, structural |
.c-blue |
Informational |
.c-amber |
Warning, in-progress |
.c-red |
Error, critical |
.c-green |
Success, complete |
Layout & viewport
Dips render in the chat column. They are single-column by design — don't author multi-column or sidebar+main layouts. The chat column is narrow on iOS / Sliccstart and on the extension side panel; multi-column dips break visually in those floats. If you need multi-column, use a sprinkle and let the user pop it to full-screen.
Cheap interactions via agent
When a dip's lick handler needs to do real work (lookup, transformation, generation) but the cone or owning scoop should NOT be woken up, route the lick to a one-shot agent call.
The flow:
- Dip emits a lick:
slicc.lick({action: 'compute', data: {input: ...}}). - The lick reaches the cone (or scoop) as a message.
- Instead of replying conversationally, the receiver shells out to
agentwith a tight allow-list and a self-contained prompt. agentreturns the answer on stdout. The receiver writes the result back to the dip via a follow-up dip or viasprinkle send(if the dip was the entry point to a sprinkle flow).
# Inside a feed_scoop reply to a dip lick:
result=$(agent /tmp "curl,jq" "Fetch <url>, return field 'price' as a number.")
# Render a fresh dip with `result` inline.
agent is the right tool here because it has no handoff: ephemeral scoops don't notify the cone on completion, so a busy cone or sprinkle-owning scoop isn't pre-empted by every dip click. See /workspace/skills/delegation/SKILL.md for the full agent reference.
Design rules
- Use S2 CSS variables for all colors (
var(--s2-content-default),var(--s2-bg-layer-2), ...). - Round all displayed numbers:
Math.round(),.toFixed(2),.toLocaleString(). - Set
stepon range sliders for clean values. - Show errors inline (not
alert()). - Sentence case always — never Title Case or ALL CAPS.
- One root
render()/calc()function triggered by all inputs. - Call
render()on load with defaults so the widget is immediately interactive.
Don't
- Don't hardcode hex colors or theme-specific values — use S2 tokens or
light-dark()(see "Light & dark mode"). - Don't use
@media (prefers-color-scheme: ...)to swap colors — the parent's theme toggle is class-based and the media query desyncs. - Don't set
height,max-height,min-height,overflow: auto, oroverflow: scrollon dip containers — the iframe auto-sizes to content; fixed heights and internal scrollers clip it. Canvases are the one exception (they need pixel dimensions for the drawing buffer). Long content belongs in a sprinkle. - Don't build custom progress bars — use
.sprinkle-progress-bar. - Don't build custom status dots — use
.sprinkle-status-light. - Don't use numbered headings (1. File Operations) inside cards — the
__headerIS the heading. - Don't put prose / markdown headings between cards — let cards stand alone.
Lick patterns
// Explicit "send to agent" button.
slicc.lick({ action: 'use-config', data: { config: getCurrentConfig() } });
// Lick on threshold crossing.
if (total > budget) {
slicc.lick({ action: 'over-budget', data: { total, budget, breakdown } });
}
// Lick on completion.
slicc.lick({ action: 'sort-complete', data: { algorithm: algo, comparisons: n } });
Payload shape:
slicc.lick(eventOrAction)accepts either a plain action string (slicc.lick('cancel')) or an{ action, data? }object. Always pack extra fields insidedata: { ... }— the cone readsevent.dataas the payload, so top-level extras either get dropped (sprinkle bridge) or folded intodataas the whole event object (dip bridge), neither of which gives the cone a predictable shape. The canonical form works identically in both.
The agent receives the lick as a structured message and can respond with prose, another dip, or spawn a scoop.