PlaygroundConfigurator
Live component playground pairing a preview stage with prop controls and a generated snippet.
Playground
A playground configuring a playground
Button
Try the intents
<Button /><PlaygroundConfigurator />01 Control Types
Every supported control in action
All Controls
<Demo /><PlaygroundConfigurator
componentName="Demo"
showHeader={false}
controls={[
{
key: 'name',
type: 'text',
label: 'Name',
defaultValue: 'Urbicon',
placeholder: 'Enter name…'
},
{ key: 'count', type: 'number', label: 'Count', defaultValue: 3, min: 0, max: 20, step: 1 },
{
key: 'opacity',
type: 'slider',
label: 'Opacity',
defaultValue: 80,
min: 0,
max: 100,
step: 5
},
{ key: 'accent', type: 'color', label: 'Accent', defaultValue: '#6366f1' },
{
key: 'variant',
type: 'dropdown',
label: 'Variant',
items: [
{ label: 'Filled', value: 'filled' },
{ label: 'Outlined', value: 'outlined' },
{ label: 'Ghost', value: 'ghost' }
],
defaultValue: 'filled'
},
{ key: 'active', type: 'boolean', label: 'Active', defaultValue: true }
]}
values={{
name: 'Urbicon',
count: 3,
opacity: 80,
accent: '#6366f1',
variant: 'filled',
active: true
}}
>
{#snippet children(values)}
<div class="flex flex-col items-center gap-3">
<div
class="text-text-on-fill flex h-20 w-20 items-center justify-center rounded-xl text-xl font-bold"
style="background: {values.accent}; opacity: {values.opacity / 100}"
>
{values.count}
</div>
<span class="text-text-primary text-sm font-medium">{values.name}</span>
<Badge variant={values.variant} intent={values.active ? 'success' : 'neutral'} size="sm">
{values.active ? 'Active' : 'Inactive'}
</Badge>
</div>
{/snippet}
</PlaygroundConfigurator>02 Conditional Controls
Controls that appear based on other values
Conditional Visibility
<Alert /><PlaygroundConfigurator
componentName="Alert"
showHeader={false}
controls={[
{
key: 'intent',
type: 'dropdown',
label: 'Intent',
items: [
{ label: 'Success', value: 'success' },
{ label: 'Warning', value: 'warning' },
{ label: 'Danger', value: 'danger' }
],
defaultValue: 'success'
},
{ key: 'dismissible', type: 'boolean', label: 'Dismissible', defaultValue: false },
{
key: 'autoDismiss',
type: 'slider',
label: 'Auto-dismiss (ms)',
defaultValue: 3000,
min: 1000,
max: 10000,
step: 500,
condition: { dependsOn: 'dismissible', equals: true }
}
]}
values={{ intent: 'success', dismissible: false, autoDismiss: 3000 }}
>
{#snippet children(values)}
<div
class="w-full max-w-sm rounded-lg border px-4 py-3 text-sm
{values.intent === 'success' ? 'border-success/30 bg-success-subtle text-success' : ''}
{values.intent === 'warning' ? 'border-warning/30 bg-warning-subtle text-warning' : ''}
{values.intent === 'danger' ? 'border-danger/30 bg-danger-subtle text-danger' : ''}"
>
<div class="flex items-center justify-between">
<span>
{values.intent === 'success' ? 'Operation completed.' : ''}
{values.intent === 'warning' ? 'Please review.' : ''}
{values.intent === 'danger' ? 'Action required!' : ''}
</span>
{#if values.dismissible}
<button
type="button"
aria-label="Dismiss"
class="rounded-xs opacity-50 transition-opacity duration-[var(--blocks-duration-fast)] hover:opacity-100 focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-current/50 focus-visible:outline-none"
>
<CloseIcon size={14} />
</button>
{/if}
</div>
{#if values.dismissible}
<span class="mt-1 block text-xs opacity-60"
>Auto-dismisses in {values.autoDismiss}ms</span
>
{/if}
</div>
{/snippet}
</PlaygroundConfigurator>03 Custom Code Generator
Pass your own code generator for tailored output
Button Builder
<Button variant="filled" intent="primary" size="md">Submit</Button><PlaygroundConfigurator
componentName="Button"
shareKey="Button-builder"
showHeader={false}
controls={[
{ key: 'label', type: 'text', label: 'Label', defaultValue: 'Submit' },
{
key: 'variant',
type: 'dropdown',
label: 'Variant',
items: [
{ label: 'Filled', value: 'filled' },
{ label: 'Outlined', value: 'outlined' },
{ label: 'Ghost', value: 'ghost' }
],
defaultValue: 'filled'
},
{
key: 'intent',
type: 'dropdown',
label: 'Intent',
items: [
{ label: 'Primary', value: 'primary' },
{ label: 'Success', value: 'success' },
{ label: 'Danger', value: 'danger' },
{ label: 'Neutral', value: 'neutral' }
],
defaultValue: 'primary'
},
{
key: 'size',
type: 'dropdown',
label: 'Size',
items: [
{ label: 'sm', value: 'sm' },
{ label: 'md', value: 'md' },
{ label: 'lg', value: 'lg' }
],
defaultValue: 'md'
},
{ key: 'disabled', type: 'boolean', label: 'Disabled', defaultValue: false }
]}
values={{
label: 'Submit',
variant: 'filled',
intent: 'primary',
size: 'md',
disabled: false
}}
codeGenerator={buttonCode}
>
{#snippet children(values)}
<Button
variant={values.variant}
intent={values.intent}
size={values.size}
disabled={values.disabled}
>
{values.label}
</Button>
{/snippet}
</PlaygroundConfigurator>04 PropDocs & VariantKeys
Info tooltips and variant badges for controls
With Prop Documentation
<Input /><PlaygroundConfigurator
componentName="Input"
showHeader={false}
propDocs={{
clearable:
'Show a clear button when the input has a value. Press Escape or click to clear.',
placeholder: 'Hint text shown when the field is empty.'
}}
variantKeys={['variant', 'size']}
controls={[
{ key: 'placeholder', type: 'text', label: 'Placeholder', defaultValue: 'Type here…' },
{
key: 'variant',
type: 'dropdown',
label: 'Variant',
items: [
{ label: 'Outlined', value: 'outlined' },
{ label: 'Filled', value: 'filled' },
{ label: 'Ghost', value: 'ghost' }
],
defaultValue: 'outlined'
},
{
key: 'size',
type: 'dropdown',
label: 'Size',
items: [
{ label: 'sm', value: 'sm' },
{ label: 'md', value: 'md' },
{ label: 'lg', value: 'lg' }
],
defaultValue: 'md'
},
{ key: 'clearable', type: 'boolean', label: 'Clearable', defaultValue: false }
]}
values={{ placeholder: 'Type here…', variant: 'outlined', size: 'md', clearable: false }}
>
{#snippet children(values)}
<Input
placeholder={values.placeholder}
variant={values.variant}
size={values.size}
clearable={values.clearable}
/>
{/snippet}
</PlaygroundConfigurator>05 Accessibility
Every control is a labelled form control
The panel renders real inputs — text fields, checkboxes, selects, sliders — each with its
own <label>. The label text is the knob's label, so a knob
named "Size" announces as "Size", and a knob whose effect is screen-reader-only should say
so there rather than reading as dead.
The stage updates without stealing focus
Changing a control re-renders the preview and leaves focus on the control. That is what makes the panel usable with a keyboard: a reader can walk the whole control set with Tab without the page pulling them back to the stage after each change.
The generated snippet is text, not an image
The code below the stage is real text in a code panel, so it is readable, selectable and copyable. A reader who cannot use the visual preview still gets the exact markup the current knob settings produce.
Reset is a button and says what it resets
A knob moved away from its default gets a marker, and the reset control is an ordinary button rather than a click target on the marker itself.
06 API Reference
Complete list of component properties and their configurations
Prop | Type | Default | Description | |
|---|---|---|---|---|
children required | Snippet<[Record<string, any>]> | — | Render snippet receiving the current values map. The argument is typed loosely
(Record<string, any>) so consumers can spread it onto child components without
type-asserting each variant key — the trade-off is that direct property access
inside the snippet is also any. Use the values prop type for typed access.
The any here is a pragmatic exception to the project-wide ban: it enables
docs-page playgrounds (<Component {...values} />) to compile without forcing
every consumer to mirror the full prop union locally. | |
class | string | — | Extra CSS classes merged onto the root element. | |
codeGenerator | (values: TValues) => string | — | Custom code generator. Falls back to auto-generated Svelte tag syntax. | |
codeSetup | CodeSetup | — | Imports and data declarations the generated snippet needs to be a complete,
copyable file.
Omit it for components whose props tell the whole story — <Button
variant="ghost">Get started</Button> needs nothing above it. Supply it for
components that are meaningless without data: a Table needs columns and
items, an A2UIView a payload. consts takes the very objects the
demo renders, so the snippet cannot fall out of step with the preview. | |
componentName | string | 'Component' | Component name used in the auto-generated code output. | |
controls | ControlDefinition[] | — | Control definitions that drive the props panel (dropdown, toggle, text, etc.). Optional, because not every component has anything to turn: the auth family has API paths and callbacks, not variant axes. Left out, the props panel is not rendered at all and the configurator carries just the stage and the code panel — which is the part those components were missing. | |
defaultCodeExpanded | boolean | — | Forces the code panel open or closed regardless of the page-wide code
switch. Leave it unset: the panel then follows CodeVisibilityStore on a
docs page, and starts collapsed where there is none (the landing hero). | |
onValuesChange | (values: TValues) => void | — | Fires after any control value changes with the full values map. | |
propDocs | Record<string, string> | — | Hand-written prop descriptions (from JSDoc). Shown as tooltip behind an info icon. | |
shareKey | string | — | Identity this playground's share links are scoped to, emitted as _pg.
A link only seeds the instance whose key it names, so a page with several
playgrounds does not decode one link into all of them.
Defaults to componentName, which already separates instances documenting
different components. Set it only to tell same-named instances on one page
apart (shareKey="Button-sizes"); the value shows up in the URL, so make
it readable. | |
showHeader | boolean | true | Show the title/subtitle header above the playground. | |
size | smmdlg | 'md' | Controls the density of the playground layout – padding, grid columns, and text size. | |
slotClasses | Partial<Record<PlaygroundConfiguratorSlotName, string>> | — | Per-slot class overrides for internal elements. | |
source | string | — | The playground's own source text, for demos whose content is **markup**
rather than data — a Card's header and footer snippets, a SplitPane's two
panes, the <SegmentItem>s inside a SegmentGroup. codeSetup cannot reach
those (there is no data form they could take), so their snippets otherwise
read <Card />: true and useless.
Pass it as a raw import, which costs nothing but the file's own text in the
chunk that already holds the playground:
svelte
import playgroundSource from './Playground.svelte?raw';
<PlaygroundConfigurator source={playgroundSource} …>
Name it playgroundSource, not self: self is window.self, so a
missing import type-checks against the global and silently passes the
Window object instead of failing.
Only the children are taken; the opening tag stays generated from the live
control values, so default props keep out of the snippet. Markup that
refers to names the snippet does not declare is dropped rather than printed
broken — declare them in codeSetup.consts to show it. | |
subtitle | string | — | Descriptive text below the title. | |
title | string | — | Heading text above the playground panel. | |
unstyled | boolean | false | Strip all default tv() styles from internal slots. | |
values | TValues | — | Current control values. Supports bind:values for two-way binding. | |
variantKeys | string[] | — | Prop names originating from tailwind-variants. Shown with a "V" indicator. | |
...HTMLAttributes<HTMLElement> inherited | HTMLAttributes | — | HTML attributes (excluding: 'children') | |
...PlaygroundConfiguratorVariantProps variant | VariantProps | — | Styling variants from PlaygroundConfiguratorVariantProps |
07 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
PlaygroundConfiguratorSlotName | type | helper | 0 | The configurator's own slots, plus the two CodePanel slots it forwards to the
embedded panel (codeToolbar → the panel's toolbar, codeDisplay → its
codeDisplay). The panel's root is reached through the own codePanel slot. | |
PlaygroundConfiguratorProps | interface | props | 0 | Interactive playground configurator for component documentation. Renders a live preview, control panel for props, and generated code block. | |
CodeSetup | interface | helper | 1 | The half of a snippet the controls know nothing about.
Some components are fully described by their props — <Button variant="ghost">
*is* the usage, and the generated tag alone is the right answer. Others need
data before the tag means anything: a Table without columns and items,
an A2UIView without a payload. For those the snippet has to carry a
<script> block, and this is how a playground supplies it.
consts takes the **actual objects the demo renders**, not a copy of them as
text — so the snippet cannot drift from the preview above it. Live control
values still flow into the tag as before. | |
PlaygroundConfiguratorSlots | type | variant | 0 | Slot names derived from the tv() config above — single source of truth for slotClasses. | |
PlaygroundConfiguratorVariantProps | type | variant | 0 | — |
08 Installation
Import
import { PlaygroundConfigurator } from '@urbicon-ui/docs';