Panel components and stylesheets
Stability:
provisional· Panel API version:1.0· SDK path:sdk/1/
Stylesheets
panel.css
SciStudio panel stylesheet (SDK major 1).
One shared sheet so every panel — built-in, packaged, or agent-written — looks like the rest of the application without re-deriving its styling. The host injects the app's design tokens (--ss-ink, --ss-canvas, --ss-ember, --ss-pine, --ss-sea, --ss-sand, plus the light/dark mode flag) into the frame; the fallbacks below keep a panel readable when it is opened standalone, for example from its panel.sample.json.
Link it from a panel document:
<link rel="stylesheet" href="../../sdk/1/panel.css" />
Classes are semantic, not utilities: describe what a thing IS (a card, a toolbar, a field) rather than how it looks, so the look can change centrally.
renderers.css
Styles for the data views in renderers.js (SDK major 1). Link it after
panel.css, which supplies the design tokens these rules use:
<link rel="stylesheet" href="../../sdk/1/panel.css" />
<link rel="stylesheet" href="../../sdk/1/renderers.css" />
Theme tokens
The SDK copies the host's --ss-* theme tokens onto the panel's root element and sets data-theme to light or dark. The stylesheets read these tokens, as space-separated RGB channels, with fallbacks for sample mode.
--ss-canvas--ss-ember--ss-ink--ss-pine--ss-sand--ss-sea
UI components: sdk/1/panel-ui.js
SciStudio panel component set (SDK major 1).
A small set of Preact components covering the shapes panels actually need, so
a panel — built-in, packaged, or agent-written — is assembled from named parts
instead of re-deriving markup and styling. Pair it with panel.css, which gives
these components the application's look through the host-injected design
tokens.
import { html, render } from "../../lib/preact-htm@3.1.1/dist/preact-standalone.module.js";
import { Panel, Card, Meta, Field, Button } from "../../sdk/1/panel-ui.js";
render(html`<${Panel}>
<${Meta} items=${["Array", "shape [3, 3]"]} />
<${Card}>…<//>
<//>`, document.getElementById("root"));
Components take plain props and children; nothing here talks to the host —
data comes from the SDK (window.scistudio) in the panel's own code. Props a
component does not name are passed to its root element.
The questionnaire components ask the user what a MiniApp should do before it
is built. The questions are data: panels/<id>/questionnaire.json, rendered
by Questionnaire, submitted through scistudio.submitAnswers, and checked
by the validate_panel agent tool. Every question is optional and every
question offers "Decide for me"; a spec cannot change either.
import { Questionnaire } from "../../sdk/1/panel-ui.js";
await scistudio.ready();
const spec = await (await fetch("questionnaire.json")).json();
render(html`<${Panel}><${Questionnaire} spec=${spec} onSubmit=${scistudio.submitAnswers} /><//>`, root);
questionnaire.json is {title, intro?, submit_label?, questions}. Each
question has id (lowercase, unique), type, prompt, and optional help:
single and multiple take options (at least two {value, label,
description?}) and allow_other; text takes multiline and
placeholder; number takes min, max, step, unit, placeholder;
range is a slider and needs min and max. An answer is {status:
"answered", value, other?}, {status: "decide_for_me"}, or {status:
"skipped"}.
Panel
The panel shell: a padded column with the standard vertical rhythm. Use it as the root of a panel.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
string |
optional | Extra class names added to the root element. |
elementRef |
object |
optional | A Preact ref that receives the root DOM element (a plain ref on a function component does not reach it). |
children |
any |
optional | Content. |
Other props are passed to the root element.
Stack
A vertical stack with the standard gap.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
string |
optional | Extra class names added to the root element. |
elementRef |
object |
optional | A Preact ref that receives the root DOM element (a plain ref on a function component does not reach it). |
children |
any |
optional | Content. |
Other props are passed to the root element.
Row
A horizontal row. Put a Spacer between children to push them apart.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
string |
optional | Extra class names added to the root element. |
elementRef |
object |
optional | A Preact ref that receives the root DOM element (a plain ref on a function component does not reach it). |
children |
any |
optional | Content. |
Other props are passed to the root element.
Spacer
Flexible empty space inside a Row.
Takes no props.
Card
The bordered surface the application uses for grouped content.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
string |
optional | Extra class names added to the root element. |
tight |
boolean |
optional | Use the smaller padding. |
elementRef |
object |
optional | A Preact ref that receives the root DOM element (a plain ref on a function component does not reach it). |
children |
any |
optional | Content. |
Other props are passed to the root element.
Meta
A metadata strip such as ["Array", "shape [3, 3]", "dtype float64"]. The first item is emphasised, matching the application's summary bars.
| Prop | Type | Default | Description |
|---|---|---|---|
items |
string[] |
optional | The entries; empty, null, and undefined entries are skipped. |
class |
string |
optional | Extra class names added to the root element. |
children |
any |
optional | Extra content after the items. |
Other props are passed to the root element.
Hint
Muted helper text.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
string |
optional | Extra class names added to the root element. |
children |
any |
optional | Content. |
Other props are passed to the root element.
Label
A small uppercase section label.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
string |
optional | Extra class names added to the root element. |
children |
any |
optional | Content. |
Other props are passed to the root element.
Badge
A rounded pill for counts and states.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
string |
optional | Extra class names added to the root element. |
children |
any |
optional | Content. |
Other props are passed to the root element.
Button
A button in the application's style. It is type="button", so it never submits a form.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
string |
optional | Extra class names added to the root element. |
primary |
boolean |
optional | Use the emphasised style for the main action. |
elementRef |
object |
optional | A Preact ref that receives the root DOM element (a plain ref on a function component does not reach it). |
children |
any |
optional | The button's label. |
Other props are passed to the root element.
Input
A text input in the application's style.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
string |
optional | Extra class names added to the root element. |
number |
boolean |
optional | Use the narrow numeric width. |
elementRef |
object |
optional | A Preact ref that receives the root DOM element (a plain ref on a function component does not reach it). |
Other props are passed to the root element.
Select
A drop-down list in the application's style.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
string |
optional | Extra class names added to the root element. |
options |
Array<string \| {value, label}> |
optional | The choices; a string is both value and label. |
children |
any |
optional | Extra <option> elements after options. |
Other props are passed to the root element.
Field
A labelled control row: a name, the control, and an optional readout. The shape the slice selectors and similar per-axis controls use.
| Prop | Type | Default | Description |
|---|---|---|---|
name |
any |
optional | The label before the control. |
readout |
any |
optional | A value shown after the control. |
class |
string |
optional | Extra class names added to the root element. |
children |
any |
optional | The control. |
Other props are passed to the root element.
ScrollArea
A scrollable data surface; put a table or grid inside.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
string |
optional | Extra class names added to the root element. |
elementRef |
object |
optional | A Preact ref that receives the scrolling element. |
children |
any |
optional | Content. |
Other props are passed to the root element.
Table
A dense table with sticky headers.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
string |
optional | Extra class names added to the root element. |
elementRef |
object |
optional | A Preact ref that receives the root DOM element (a plain ref on a function component does not reach it). |
children |
any |
optional | The <thead> and <tbody>. |
Other props are passed to the root element.
Legend
A value-scale legend: minimum label, colour ramp, optional middle label, maximum label.
| Prop | Type | Default | Description |
|---|---|---|---|
min |
any |
optional | Label at the low end. |
mid |
any |
optional | Label in the middle; omitted when undefined. |
max |
any |
optional | Label at the high end. |
stops |
string[] |
optional | CSS colours forming the ramp, low to high. |
class |
string |
optional | Extra class names added to the root element. |
Other props are passed to the root element.
ItemGrid
A responsive grid of Item cards, for collection or composite children.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
string |
optional | Extra class names added to the root element. |
elementRef |
object |
optional | A Preact ref that receives the root DOM element (a plain ref on a function component does not reach it). |
children |
any |
optional | Content. |
Other props are passed to the root element.
Item
One clickable item card: a name over a secondary line.
| Prop | Type | Default | Description |
|---|---|---|---|
name |
any |
optional | The item's name. |
sub |
any |
optional | The secondary line, such as its type. |
title |
string |
optional | Tooltip text. |
class |
string |
optional | Extra class names added to the root element. |
elementRef |
object |
optional | A Preact ref that receives the root DOM element (a plain ref on a function component does not reach it). |
children |
any |
optional | Extra content. |
Other props are passed to the root element.
ListRow
One full-width clickable row: a label over a value, and an optional trailing hint on the right. The shape a slot inventory or any name/type listing uses.
| Prop | Type | Default | Description |
|---|---|---|---|
label |
any |
optional | The row's label. |
value |
any |
optional | The value under the label. |
trailing |
any |
optional | A hint on the right, such as Preview →. |
class |
string |
optional | Extra class names added to the root element. |
elementRef |
object |
optional | A Preact ref that receives the root DOM element (a plain ref on a function component does not reach it). |
children |
any |
optional | Extra content under the value. |
Other props are passed to the root element.
Pager
Plain navigation for complete, paginated data: a label such as rows 1–50 of 200 · page 1/4 between Previous and Next. Paging is navigation, never a sign that data is partial, so do not show a truncation warning beside it.
| Prop | Type | Default | Description |
|---|---|---|---|
page |
number |
required | The current page, from 1. |
totalPages |
number |
required | The number of pages. |
label |
string |
optional | The text between the buttons; defaults to page <page>/<totalPages>. |
onPrev |
function |
optional | Called by Previous; disabled on page 1. |
onNext |
function |
optional | Called by Next; disabled on the last page. |
class |
string |
optional | Extra class names added to the root element. |
Other props are passed to the root element.
Icon
A lucide icon by name, in lucide's PascalCase (ChevronRight) or kebab-case (chevron-right). Needs the lucide library loaded as window.lucide; renders nothing when the library is absent or the name is unknown, so a missing icon never breaks a panel.
| Prop | Type | Default | Description |
|---|---|---|---|
name |
string |
required | The icon name. |
class |
string |
optional | Extra class names added to the root element. |
Other props are passed to the root element.
ErrorState
An error state (role="alert"). Use it for a read that failed, never for complete data that is paged.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
string |
optional | Extra class names added to the root element. |
children |
any |
optional | The message. |
Other props are passed to the root element.
LoadingState
A loading state (aria-busy). Show it until the first data arrives, so a slow read does not flash an empty surface.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
string |
optional | Extra class names added to the root element. |
children |
any |
optional | The message; defaults to Loading…. |
Other props are passed to the root element.
EmptyState
An empty state for a target that genuinely has nothing to show.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
string |
optional | Extra class names added to the root element. |
children |
any |
optional | The message. |
Other props are passed to the root element.
SingleChoiceQuestion
A single-choice question: one pill per option, plus "Decide for me". Choosing the chosen option again clears it back to skipped.
| Prop | Type | Default | Description |
|---|---|---|---|
question |
object |
required | The question from the spec: {id, prompt, help?, options, allow_other?}. |
answer |
object |
optional | The current answer; skipped when absent. |
onChange |
function |
optional | Receives the new answer. |
MultipleChoiceQuestion
A multiple-choice question: pills that toggle independently, plus "Decide for me". Clearing every pill returns the question to skipped.
| Prop | Type | Default | Description |
|---|---|---|---|
question |
object |
required | The question from the spec: {id, prompt, help?, options, allow_other?}. |
answer |
object |
optional | The current answer; skipped when absent. |
onChange |
function |
optional | Receives the new answer. |
TextQuestion
A free-text question, one line or several, plus "Decide for me". Empty text is skipped.
| Prop | Type | Default | Description |
|---|---|---|---|
question |
object |
required | The question from the spec: {id, prompt, help?, multiline?, placeholder?}. |
answer |
object |
optional | The current answer; skipped when absent. |
onChange |
function |
optional | Receives the new answer. |
NumberQuestion
A number question — a number box, or a slider when the spec's type is range — plus "Decide for me". An empty box is skipped.
| Prop | Type | Default | Description |
|---|---|---|---|
question |
object |
required | The question from the spec: {id, type, prompt, help?, min?, max?, step?, unit?, placeholder?}. |
answer |
object |
optional | The current answer; skipped when absent. |
onChange |
function |
optional | Receives the new answer. |
Question
One question of any type, drawn by the component for its type.
| Prop | Type | Default | Description |
|---|---|---|---|
question |
object |
required | The question from the spec. |
answer |
object |
optional | The current answer; skipped when absent. |
onChange |
function |
optional | Receives the new answer. |
SubmitBar
The bar under the questions: a Submit button that is enabled with any number of answers, and the message a submit left behind.
| Prop | Type | Default | Description |
|---|---|---|---|
label |
string |
optional | The button text; defaults to Submit. |
busy |
boolean |
optional | A submit is in flight; the button is disabled only then. |
message |
string |
optional | Text beside the button, such as the submit result's message. |
kind |
string |
optional | "done", "return" (go back to your AI chat), or "error"; colours the message. |
onSubmit |
function |
optional | Called when the button is pressed. |
Questionnaire
A whole questionnaire from its spec: title, intro, every question, and the submit bar. A spec that breaks a rule renders an error state that lists each problem instead of a form. Submitting sends one answer per question — skipped for those left alone — to onSubmit and shows the message it resolves with; the result's notified: false shows as a reminder to return to the AI chat.
| Prop | Type | Default | Description |
|---|---|---|---|
spec |
object |
required | The parsed questionnaire.json. |
onSubmit |
function |
optional | Receives the answers keyed by question id and returns a promise; pass scistudio.submitAnswers. |
initialAnswers |
object |
optional | Answers to start from, keyed by question id. |
class |
string |
optional | Extra class names added to the root element. |
Other props are passed to the root element.
Data views: sdk/1/renderers.js
The data views SciStudio's own previews are built from, importable by any
panel. Load panel.css and renderers.css, and use the vendored Preact at
../../lib/preact-htm@3.1.1/dist/preact-standalone.module.js; a second
Preact instance breaks hooks.
import { html, render } from "../../lib/preact-htm@3.1.1/dist/preact-standalone.module.js";
import { ArrayView, TextView } from "../../sdk/1/renderers.js";
The views take values, controlled state, and callbacks. They never call
window.scistudio: the panel reads the data, keeps the state, and decides
what saving or opening does. Importing a module mounts nothing, and several
instances can share a page. Every view accepts error, which takes
precedence over data, and shows its loading state while its main data prop
is absent. Pass new objects when values change; inputs are not mutated.
Each view also lives in its own renderer-*.js module. Other named exports
of those modules are helpers, not part of this contract.
ArrayView
Module: sdk/1/renderer-array.js
The numeric heatmap for an array of any dimensionality: one plane is shown, and every other axis gets an index control.
Give it either local values in data, or a caller-owned plane and tile for a bounded remote array read with array.plane and array.tile. Local data is windowed automatically; with plane/tile the caller reads the window that onScroll asks for.
| Prop | Type | Default | Description |
|---|---|---|---|
data |
number \| Array \| TypedArray |
optional | Local values: a scalar, rectangular nested arrays, or a typed numeric array. Flat data with shape is indexed row-major. |
shape |
number[] |
optional | Shape of data; inferred from nested arrays when omitted. Must match the number of values. |
axes |
string[] |
optional | Axis names, one per dimension. |
dtype |
string |
optional | Data type label shown in the summary; number for local data when omitted. |
indices |
object |
optional | Selected index per non-displayed axis, {axis: index}. |
onSliceChange |
function |
optional | (axis, index) when the reader moves an index control; update indices in response. |
plane |
object |
optional | An array.plane result: source_shape, source_dtype, axes, slice_axes, vmin, vmax. |
tile |
object |
optional | The loaded window {values, y0, x0}, values as rows of cells. |
rowHeight |
number |
optional | Row height in pixels used to place the window. |
scrollRef |
object |
optional | A Preact ref for the scrolling element. |
onScroll |
function |
optional | (event) when the surface scrolls; read the window it now shows. |
error |
string |
optional | A displayable message. It takes precedence over any data, so a failed read never leaves earlier values looking current. |
DataFrameView
Module: sdk/1/renderer-dataframe.js
A paged, sortable table. The caller reads each page (for example with table.page) and passes it in; the view asks for another page or sort order through onQueryChange.
| Prop | Type | Default | Description |
|---|---|---|---|
data |
object |
optional | The page: {columns, rows, total?, total_rows?, page?, page_size?, total_pages?, sort?: {by, direction}}. The loading state shows until it is given. |
query |
object |
{ page: 1, pageSize: 50 } |
The requested {page, pageSize, sortBy?, sortDir?}. |
loading |
boolean |
false |
Marks a read in flight. |
error |
string |
optional | A displayable message. It takes precedence over any data, so a failed read never leaves earlier values looking current. |
onQueryChange |
function |
() => {} |
(nextQuery) when the reader pages or sorts; sorting returns to page 1. |
SeriesView
Module: sdk/1/renderer-series.js
A series as a line chart or a table of every row. Rows without a finite value are drawn as breaks in the line, listed in a notice, and shown as they are in the table.
| Prop | Type | Default | Description |
|---|---|---|---|
data |
object |
optional | A series.points result {index, values, offset?, total?}, several pages with their index and values joined in order, or computed values and index arrays. Every row is shown; nothing is sampled. |
loading |
boolean |
false |
Set while further pages are still being read; the view says how many rows of data.total it holds. |
mode |
"chart" \| "table" |
"chart" |
Which view to show. |
onModeChange |
function |
() => {} |
(mode) when the reader switches view. |
error |
string |
optional | A displayable message. It takes precedence over any data, so a failed read never leaves earlier values looking current. |
plotly |
object |
globalThis.Plotly |
The Plotly library for chart mode; load plotly@2.35.3 from the shared libraries. Table mode needs none. |
TextView
Module: sdk/1/renderer-text.js
A text document, shown literally in a scrolling surface.
| Prop | Type | Default | Description |
|---|---|---|---|
text |
string |
"" |
The text read so far. |
meta |
object |
{} |
The latest text.chunk result, or {total_bytes?, encoding?, next_offset?}; null shows the loading state. |
done |
boolean |
true |
false while more text is still being read. |
hasMore |
boolean |
false |
More of the document remains and is not being read right now; shows how much is on screen and a Read more control. |
onReadMore |
function |
optional | Called by Read more; read the next chunks and append them to text. |
error |
string |
optional | A displayable message. It takes precedence over any data, so a failed read never leaves earlier values looking current. |
ArtifactView
Module: sdk/1/renderer-artifact.js
A stored file: its path, MIME type, and size, with the image inline when it is one.
| Prop | Type | Default | Description |
|---|---|---|---|
info |
object |
optional | An artifact.info result {name?, path?, mime_type?, size?}; the loading state shows until it is given. |
url |
string |
optional | URL of the file's bytes, such as an artifact.file result's url or a caller-owned blob URL. |
imageFailed |
boolean |
false |
Set after the image failed to load, to explain why nothing is shown. |
onImageError |
function |
() => {} |
Called when the inline image fails to load. |
error |
string |
optional | A displayable message. It takes precedence over any data, so a failed read never leaves earlier values looking current. |
MetadataView
Module: sdk/1/renderer-base.js
The fallback view for any data object: its type and ancestry, shape and dtype, stored file, and recorded metadata.
| Prop | Type | Default | Description |
|---|---|---|---|
meta |
object |
optional | A metadata result {type_chain?, shape?, dtype?, metadata?}; the loading state shows until it is given. |
file |
object |
optional | The stored file {name?, path?, mime_type?, size?, url?}, when there is one. |
imageFailed |
boolean |
false |
Set after the image failed to load. |
onImageError |
function |
() => {} |
Called when the inline image fails to load. |
error |
string |
optional | A displayable message. It takes precedence over any data, so a failed read never leaves earlier values looking current. |
CollectionView
Module: sdk/1/renderer-collection.js
A grid of a collection's items. The caller owns paging and what opening an item does.
| Prop | Type | Default | Description |
|---|---|---|---|
items |
object[] |
[] |
The items read so far, {ref?, data_ref?, display_name?, type_name?, metadata?}. |
count |
number |
items.length |
Total number of items in the collection. |
itemType |
string |
"items" |
What the items are called in the summary. |
loading |
boolean |
false |
Marks a read in flight. |
hasMore |
boolean |
false |
Show the Show more control. |
onLoadMore |
function |
optional | Called by Show more. |
error |
string |
optional | A displayable message. It takes precedence over any data, so a failed read never leaves earlier values looking current. |
onOpen |
function |
optional | (ref, item) when the reader opens an item; items are not clickable without it. |
CompositeView
Module: sdk/1/renderer-composite.js
The slot inventory of a composite, one row per slot.
| Prop | Type | Default | Description |
|---|---|---|---|
slots |
object[] \| null |
[] |
The slots {name, type_name?, ref?}, as composite.slots returns them; null shows the loading state. |
error |
string |
optional | A displayable message. It takes precedence over any data, so a failed read never leaves earlier values looking current. |
onOpen |
function |
optional | (ref, slot) when the reader opens a slot; rows are not clickable without it. |
PlotView
Module: sdk/1/renderer-plot.js
A rendered plot figure with zoom, PDF paging, and a Save control for the formats the plot was rendered in.
| Prop | Type | Default | Description |
|---|---|---|---|
info |
object |
optional | An artifact.info result for the plot {name?, mime_type?, formats?}; the loading state shows until it is given. |
file |
object |
optional | The figure: {mime_type?, url?} for an image, or {mime_type, data: ArrayBuffer} for a PDF. |
zoom |
number |
1 |
Zoom factor, from 0.5 to 4 in 25% steps. |
onZoom |
function |
() => {} |
(next) when the reader zooms. |
saveFormat |
string |
optional | The selected save format, one of info.formats. |
onSaveFormatChange |
function |
() => {} |
(format) when the reader picks a format. |
page |
number |
1 |
Current PDF page, from 1. |
onPageChange |
function |
() => {} |
(page) when the reader turns a PDF page. |
saving |
boolean |
false |
Marks a save in flight. |
onSave |
function |
optional | (format) when the reader saves; read that format's own bytes (for example artifact.file with variant). Save is disabled without it. |
error |
string |
optional | A displayable message. It takes precedence over any data, so a failed read never leaves earlier values looking current. |
libBaseUrl |
string |
optional | scistudio.libBaseUrl; needed to render PDF figures. |