Form Inputs
This category covers the reusable form input controls used across the Praxrr UI. All components live under $ui/ (source: packages/praxrr-app/src/lib/client/ui/) and use Svelte 4 export let prop declarations. Some forward classic on:* events via createEventDispatcher, while others expose onchange callback props — the tables below note which pattern each component uses.
FormInput
Section titled “FormInput”Labeled text field wrapper supporting text/number/email/password/url/time/date inputs, a multi-line <textarea>, an auto-resizing wrap mode, a password visibility toggle, and a suffix slot.
import FormInput from '$ui/form/FormInput.svelte';| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
label |
string |
— | Yes | Field label text (rendered in a <label>). |
description |
string |
'' |
No | Helper text shown under the label. |
placeholder |
string |
'' |
No | Input placeholder. |
value |
string |
'' |
No | Bindable input value. |
textarea |
boolean |
false |
No | Render a multi-line <textarea> instead of <input>. |
rows |
number |
6 |
No | Textarea row count (only for textarea mode). |
type |
'text' | 'number' | 'email' | 'password' | 'url' | 'time' | 'date' |
'text' |
No | Native input type (ignored when textarea/wrap; overridden by private_). |
required |
boolean |
false |
No | Marks field required and renders a red asterisk. |
hideLabel |
boolean |
false |
No | Visually hide the label (sr-only); also collapses container spacing when no description. |
name |
string |
'' |
No | Input name; also the id and label for target when id is not set. |
id |
string | undefined |
undefined |
No | Input/textarea id and label for target. Falls back to name. |
autocomplete |
string |
'' |
No | Autocomplete attribute (only applied on non-textarea/non-wrap inputs). |
private_ |
boolean |
false |
No | Password mode: masks input and adds an Eye/EyeOff visibility toggle button. |
readonly |
boolean |
false |
No | Read-only field with muted styling. |
mono |
boolean |
false |
No | Use monospace font. |
disabled |
boolean |
false |
No | Disable the input with muted styling. |
showPrivateToggle |
boolean |
true |
No | In private_ mode, show/hide the reveal button. |
wrap |
boolean |
false |
No | Render a single-visual-line textarea that auto-resizes to content (via the autoResize action). |
size |
'sm' | 'md' | 'lg' |
'md' |
No | Sizing/padding/rounding preset. |
inputClass |
string |
'' |
No | Extra CSS classes appended to the input element. |
inputElement |
HTMLInputElement | HTMLTextAreaElement | null |
null |
No | Bindable reference to the underlying input/textarea element. |
<script> import FormInput from '$ui/form/FormInput.svelte'; import Badge from '$ui/badge/Badge.svelte';
let demoFormPassword = ''; let val = '';</script>
<FormInput label="Password" name="apiKey" private_ bind:value={demoFormPassword} on:input={(e) => (val = e.detail)}> <svelte:fragment slot="suffix"><Badge>copy</Badge></svelte:fragment></FormInput>Events & Slots
Section titled “Events & Slots”on:input— dispatched with the current value string (detail).on:focus/on:blur— dispatched (void).suffixslot — rendered as an absolutely-positioned adornment inside the input (triggers a relative wrapper and right-padding).
Variants & Notes
Section titled “Variants & Notes”- Sizes:
sm,md,lg. Modes:textarea,private_(password),wrap(auto-resize). monofont;readonly/disabledmuted states; time/date types get a dark color-scheme.- Attributes not declared as props (
$$restProps, e.g.role,aria-*) are spread onto the<input>/<textarea>in every render branch, not onto the wrapper.SearchDropdownuses this to pass its combobox attributes. Setidwhennameis empty or shared, so the label stays associated with the field. - Svelte 4 syntax (
export let,createEventDispatcher,on:*,$$slots.suffix). Render-branch precedence: textarea, thenprivate_, then wrap, then plain input. - In
private_mode the input type toggles between'text'/'password'regardless oftype;hasSuffixderives from$$slots.suffix.
NumberInput
Section titled “NumberInput”Numeric input with custom stepper up/down buttons, min/max/step clamping, partial-input tolerance, compact and responsive auto-compact sizing, and mono/sans font options. Unlabeled bare control.
import NumberInput from '$ui/form/NumberInput.svelte';| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
name |
string |
— | Yes | Input name attribute. |
id |
string |
name |
No | Input id (defaults to the name prop). |
value |
number | undefined |
undefined |
No | Bindable numeric value; undefined means empty. |
min |
number | undefined |
undefined |
No | Minimum allowed value (clamps input and blocks decrement). |
max |
number | undefined |
undefined |
No | Maximum allowed value (clamps input and blocks increment). |
step |
number |
1 |
No | Increment/decrement step. |
required |
boolean |
false |
No | Native required attribute. |
disabled |
boolean |
false |
No | Disable input and stepper buttons. |
placeholder |
string |
'' |
No | Placeholder text. |
font |
'mono' | 'sans' | undefined |
undefined |
No | Font family override. |
compact |
boolean |
false |
No | Smaller sizing with narrower stepper buttons. |
responsive |
boolean |
false |
No | Auto-switch to compact and hide steppers below 1280px (via matchMedia). |
onchange |
((value: number) => void) | undefined |
undefined |
No | Callback prop fired on committed value changes (not fired for undefined/cleared). |
onMinBlocked |
(() => void) | undefined |
undefined |
No | Callback when decrement is blocked at min. |
onMaxBlocked |
(() => void) | undefined |
undefined |
No | Callback when increment is blocked at max. |
<script> import NumberInput from '$ui/form/NumberInput.svelte';
let demoNumber = 50; let score = 50;</script>
<NumberInput name="score" bind:value={demoNumber} min={0} max={100} step={1} on:change={(e) => e.detail !== undefined && (score = e.detail)}/>Events & Slots
Section titled “Events & Slots”on:change— dispatched on every commit, includingundefinedwhen the field is cleared on blur.onchangecallback prop — receives numbers only (neverundefined).
Variants & Notes
Section titled “Variants & Notes”- Default /
compact/responsive(auto-compact plus hidden steppers on small screens).font:monoorsans. - Internally uses Svelte 4
on:input/on:focus/on:blurwithbind:value={inputValue}(string state). - Allows partial inputs (
-,.,-.) while typing; commits and clamps on blur. Emits bothdispatch('change')and theonchangecallback.
DateInput
Section titled “DateInput”Composite YYYY-MM-DD date picker built from three SearchDropdown selects (month/day/year) with per-month day-count clamping and a configurable year range.
import DateInput from '$ui/form/DateInput.svelte';| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
label |
string |
— | Yes | Field label. |
description |
string |
'' |
No | Helper text under the label. |
value |
string |
'' |
No | Bindable value in YYYY-MM-DD format. |
required |
boolean |
false |
No | Renders a red asterisk on the label. |
hideLabel |
boolean |
false |
No | Hide the label entirely (label not rendered). |
name |
string |
'' |
No | Base name; child selects get -month/-day/-year suffixes. |
readonly |
boolean |
false |
No | Treated the same as disabled (effectiveDisabled). |
disabled |
boolean |
false |
No | Disable all three selects. |
size |
'sm' | 'md' | 'lg' |
'md' |
No | Size passed to each SearchDropdown. |
minYear |
number | undefined |
undefined |
No | Minimum selectable year (default currentYear - 10). |
maxYear |
number | undefined |
undefined |
No | Maximum selectable year (default currentYear + 10). |
<script> import DateInput from '$ui/form/DateInput.svelte';
let dateValue = '';</script>
<DateInput label="Release date" name="release" bind:value={dateValue} minYear={2000} maxYear={2030} on:change={(e) => (dateValue = e.detail)}/>Events & Slots
Section titled “Events & Slots”on:inputandon:change— both fire with the newYYYY-MM-DDstring when any field changes.
Variants & Notes
Section titled “Variants & Notes”- Sizes:
sm,md,lg. Year range configurable viaminYear/maxYear(auto-normalized somin <= max). - An empty value auto-initializes to today’s date on mount. Day options recompute from days-in-month; the day clamps down if the month/year shrinks.
- Uses
SearchDropdownwithfullWidth={false}.
TimeInput
Section titled “TimeInput”Composite HH:MM (24-hour) time picker built from two SearchDropdown selects (hour 00-23, minute 00-59) with fixed-width fields.
import TimeInput from '$ui/form/TimeInput.svelte';| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
label |
string |
— | Yes | Field label. |
description |
string |
'' |
No | Helper text under the label. |
value |
string |
'' |
No | Bindable value in HH:MM format. |
required |
boolean |
false |
No | Renders a red asterisk on the label. |
hideLabel |
boolean |
false |
No | Hide the label entirely. |
name |
string |
'' |
No | Base name; child selects get -hour/-minute suffixes. |
readonly |
boolean |
false |
No | Disables the selects (combined with disabled). |
disabled |
boolean |
false |
No | Disable both selects. |
size |
'sm' | 'md' | 'lg' |
'md' |
No | Size passed to each SearchDropdown. |
fieldWidthRem |
number |
5 |
No | Width in rem of each hour/minute field wrapper. |
<script> import TimeInput from '$ui/form/TimeInput.svelte';
let dailyTime = '00:00';</script>
<TimeInput label="Time" hideLabel name="cron-daily-time" fieldWidthRem={5} bind:value={dailyTime} on:input={(e) => (dailyTime = e.detail)}/>Events & Slots
Section titled “Events & Slots”on:input— fires with the newHH:MMstring. There is no separate change event.
Variants & Notes
Section titled “Variants & Notes”- Sizes:
sm,md,lg; configurable field width viafieldWidthRem. - Defaults hour/minute to
00when the value is unparseable. Parses/clamps hour 0-23 and minute 0-59. - The container is
inline-flex flex-col. Used heavily byCronInput.
SearchDropdown
Section titled “SearchDropdown”Single-select searchable combobox built on FormInput. Filters options by typed text, is keyboard navigable (Arrow/Enter/Escape), offers a clear button, and supports a custom item slot. The input carries the ARIA combobox pattern.
import SearchDropdown from '$ui/form/SearchDropdown.svelte';| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
options |
Array<{ value: string; label: string; [key: string]: unknown }> |
[] |
No | Selectable options (value + label, plus arbitrary extra fields). |
value |
string | null |
null |
No | Bindable selected option value. |
placeholder |
string |
'Search...' |
No | Input placeholder. |
disabled |
boolean |
false |
No | Disable the control. |
fullWidth |
boolean |
true |
No | Apply w-full to the wrapper. |
label |
string |
'Search' |
No | Field label (passed to FormInput). |
description |
string |
'' |
No | Helper text (passed to FormInput). |
name |
string |
'' |
No | Input name. |
hideLabel |
boolean |
true |
No | Hide the label (hidden by default). |
size |
'sm' | 'md' | 'lg' |
'md' |
No | Size passed to FormInput. |
constrainMenuHeight |
boolean |
true |
No | Cap dropdown menu height at max-h-60 with scroll. |
<script> import SearchDropdown from '$ui/form/SearchDropdown.svelte';
let selected = null; let demoAutoOptions = [ { value: 'radarr', label: 'Radarr' }, { value: 'sonarr', label: 'Sonarr' }, ];</script>
<SearchDropdown options={demoAutoOptions} bind:value={selected} placeholder="Search arrs..." on:change={(e) => (selected = e.detail)}> <svelte:fragment slot="item" let:option>{option.label}</svelte:fragment></SearchDropdown>Events & Slots
Section titled “Events & Slots”on:change— fires with the selected option value (empty string''when cleared).valueis bindable, but only the dispatch communicates selection.itemslot (let:option) — custom rendering of each option row (defaults tooption.label).
Variants & Notes
Section titled “Variants & Notes”- Sizes:
sm,md,lg;fullWidth;constrainMenuHeighton/off. - Closes on outside click (
use:clickOutside) and on blur after 100ms. The highlight index syncs to the selected or first filtered option. selectOptiondispatches change but does not itself setvalue(the parent must bind or handle change). Renders a clear button (aria-label="Clear selection") inFormInput’s suffix slot when a value is set; clearing refocuses the input.- ARIA: the input (forwarded through
FormInput) hasrole="combobox",aria-haspopup="listbox",aria-autocomplete="list",aria-controls(generated listbox id),aria-expanded(open with at least one match), andaria-activedescendantpointing at the highlighted option. Its id isname, or a generated id whennameis empty. The menu isrole="listbox"labelled bylabel; options arerole="option"buttons withtabindex="-1"andaria-selected, so focus stays in the input. - Arrow keys and Enter are handled only when the input is the event target; Escape (from anywhere inside the wrapper, when open) is prevented and stopped, closes the menu, and refocuses the input, so it does not also close an enclosing modal.
MarkdownInput
Section titled “MarkdownInput”Markdown-aware <textarea> or single-line input with a formatting toolbar (bold/italic/code/link/bullet/ordered list), Ctrl+B/Ctrl+I shortcuts, and a live rendered preview toggle (via marked).
import MarkdownInput from '$ui/form/MarkdownInput.svelte';| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
value |
string |
'' |
No | Bindable text content. |
placeholder |
string |
'' |
No | Placeholder text. |
label |
string |
'' |
No | Field label (rendered only if set). |
description |
string |
'' |
No | Helper text under the label. |
rows |
number |
6 |
No | Textarea rows (multiline mode). |
multiline |
boolean |
true |
No | Render a textarea (true) vs. a single-line input (false). |
markdown |
boolean |
true |
No | Enable the toolbar, shortcuts, and preview toggle. |
required |
boolean |
false |
No | Renders a red asterisk on the label. |
disabled |
boolean |
false |
No | Disable input and toolbar buttons. |
name |
string |
'' |
No | Field name; when set in preview mode a hidden input mirrors the value. |
id |
string |
name |
No | Element id (defaults to the name prop). |
onchange |
((value: string) => void) | undefined |
undefined |
No | Callback fired on input and on toolbar insertions. |
<script> import MarkdownInput from '$ui/form/MarkdownInput.svelte';
let demoMarkdown = '';</script>
<MarkdownInput label="Description" description="Supports **markdown**" bind:value={demoMarkdown} rows={4} onchange={(v) => (demoMarkdown = v)}/>Events & Slots
Section titled “Events & Slots”onchangecallback prop (nocreateEventDispatcher). Fires with the new value string.
Variants & Notes
Section titled “Variants & Notes”- Multiline textarea vs. single-line input;
markdownon/off (plain input when off); preview vs. edit mode toggle. - Preview renders
marked.parse(value)via{@html}(no sanitization). The toolbar is disabled whendisabledorshowPreview. - Ctrl/Cmd+B and +I insert bold/italic. Link insertion wraps the selection or inserts a placeholder. When
markdown={true}the input getsrounded-t-none/border-t-0to join the toolbar.
TagInput
Section titled “TagInput”Chip-style tag entry field. Type and press Enter to add a tag (rendered as an accent Badge); click the X or press Backspace on an empty input to remove. Duplicate detection is case-insensitive with a throttled alert toast.
import TagInput from '$ui/form/TagInput.svelte';| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
tags |
string[] |
[] |
No | Bindable array of tag strings. |
placeholder |
string |
'Type and press Enter to add tags' |
No | Input placeholder. |
onchange |
((tags: string[]) => void) | undefined |
undefined |
No | Callback fired with the new tags array on add/remove. |
<script> import TagInput from '$ui/form/TagInput.svelte';
let demoTags = [];</script>
<TagInput bind:tags={demoTags} placeholder="Add a tag..." onchange={(t) => (demoTags = t)} />Events & Slots
Section titled “Events & Slots”onchangecallback prop (nocreateEventDispatcher). Fires with the updatedstring[].
Variants & Notes
Section titled “Variants & Notes”- Duplicates are compared lowercased; a rejected duplicate shows
alertStore.add('error', 'Tag already added.'), throttled to once per 1500ms per tag. - The stored tag preserves original case (trimmed). Backspace on an empty input removes the last tag. Uses a fixed internal input id
tags-input.
IconCheckbox
Section titled “IconCheckbox”Icon-rendering checkbox button (role=checkbox) with named/hex/CSS-var colors, filled or outline variants, and three shapes. The icon shows only when checked. In decorative mode it renders a non-interactive indicator instead of a button.
import IconCheckbox from '$ui/form/IconCheckbox.svelte';| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
checked |
boolean |
false |
No | Bindable checked state. |
icon |
ComponentType |
— | Yes | Lucide/Svelte icon component rendered (size 14) when checked. |
color |
'accent' | 'blue' | 'green' | 'red' | 'neutral' | #${string} | var(--${string}) |
'accent' |
No | Named color, hex (e.g. #FFC230), or CSS var (e.g. var(--arr-radarr-color)). Custom colors apply via inline style. |
shape |
'square' | 'circle' | 'rounded' |
'rounded' |
No | Corner shape (square=rounded-none, circle=rounded-full, rounded=rounded-lg). |
disabled |
boolean |
false |
No | Disable the button (opacity plus not-allowed cursor). |
variant |
'filled' | 'outline' |
'filled' |
No | Filled background vs. outlined with a colored icon. |
iconColor |
string |
'' |
No | Override icon color class (else white for filled / color for outline). |
stopPropagation |
boolean |
false |
No | Call event.stopPropagation() on click before dispatching. |
title |
string | undefined |
undefined |
No | Native title tooltip. |
label |
string | undefined |
undefined |
Yes unless decorative |
Accessible name applied as aria-label on the checkbox button. Required for the interactive button; not allowed in decorative mode. |
decorative |
boolean |
false |
No | Render an aria-hidden <span> with the same styling instead of the checkbox button. No role, aria-checked, title, or click event. Use inside another control that already exposes the state. |
<script> import IconCheckbox from '$ui/form/IconCheckbox.svelte'; import { Check } from 'lucide-svelte';
let enabled = false;</script>
<IconCheckbox icon={Check} color="green" variant="outline" label="Enable feature" bind:checked={enabled} on:click={() => (enabled = !enabled)}/>Events & Slots
Section titled “Events & Slots”on:click— fires with theMouseEvent. It does NOT auto-togglechecked; the caller manages state.
Variants & Notes
Section titled “Variants & Notes”variant:filledoroutline;shape:square,circle, orrounded; colorsaccent/blue/green/red/neutralplus hex and CSS var.- Named colors resolve to Tailwind class maps; hex/var colors resolve via an inline
buttonStyle. Only the five named colors are supported — other names fall back toaccent. - The component does not mutate
checkeditself, so a standaloneon:clickhandler must toggle it. - Use
decorativewhen the checkbox is a visual indicator inside a larger interactive element (for example a row<button aria-pressed>inCustomGroupManager); this avoids nesting a button inside a button and announcing the state twice. The parent control must expose the state itself.
Toggle
Section titled “Toggle”Card-style switch (role=switch): a bordered clickable row with optional label text and an IconCheckbox on the right. Keyboard-activatable (Enter/Space) and self-toggling.
import Toggle from '$ui/toggle/Toggle.svelte';| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
checked |
boolean |
false |
No | Bindable on/off state. |
disabled |
boolean |
false |
No | Disable interaction (muted, non-focusable). |
label |
string |
'' |
No | Visible label text (also used as the aria-label fallback). |
ariaLabel |
string |
'Toggle' |
No | Accessible label when there is no visible label. |
color |
'accent' | 'amber' | 'green' | 'red' | 'neutral' |
'accent' |
No | Legacy color prop mapped to the IconCheckbox color (amber maps to hex #F59E0B). |
icon |
ComponentType |
Check |
No | Icon shown when checked (passed to IconCheckbox); defaults to the lucide Check icon. |
checkboxColor |
'accent' | 'blue' | 'green' | 'red' | 'neutral' | #${string} | var(--${string}) | '' |
'' |
No | Direct IconCheckbox color override; when set it takes precedence over color. |
shape |
'square' | 'circle' | 'rounded' |
'circle' |
No | IconCheckbox shape. |
variant |
'filled' | 'outline' |
'filled' |
No | IconCheckbox variant. |
iconColor |
string |
'' |
No | IconCheckbox icon color override. |
The interactive island below reproduces the switch card interaction (self-toggling row plus icon indicator).
State: checked = false
<script> import Toggle from '$ui/toggle/Toggle.svelte';
let enabled = false;</script>
<Toggle color="green" bind:checked={enabled} label="Enable feature" on:change={(e) => (enabled = e.detail)} />Events & Slots
Section titled “Events & Slots”on:changeandon:checked— both fire with the new checked value on toggle.
Variants & Notes
Section titled “Variants & Notes”coloraccent/amber/green/red/neutral (legacy) orcheckboxColoroverride;shape/variantpass through toIconCheckbox.- Unlike the bare
IconCheckbox,Toggleself-mutatescheckedinhandleToggle.color'amber'has noIconCheckboxequivalent, so it is remapped to hex#F59E0B. resolvedLabel = label || ariaLabel. The whole card is the switch; the innerIconCheckboxisdecorative(anaria-hiddenindicator), so the switch is the only control.
RangeScale
Section titled “RangeScale”Draggable multi-marker range track. Renders color-coded draggable dots with badge labels, enforces step snapping and a minimum separation between markers, and supports optional unit/unlimited/transform display formatting. Horizontal or vertical.
import RangeScale from '$ui/form/RangeScale.svelte';| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
orientation |
'horizontal' | 'vertical' |
'horizontal' |
No | Track orientation. |
direction |
'start' | 'end' |
'start' |
No | start puts min at left/top; end reverses the axis. |
min |
number |
0 |
No | Scale minimum. |
max |
number |
100 |
No | Scale maximum. |
step |
number |
1 |
No | Value snapping step. |
minSeparation |
number |
20 |
No | Minimum pixels enforced between adjacent markers during drag. |
markers |
Marker[] |
[] |
No | Bindable array of markers ({ id, label, color, value }). Mutated in place on drag. |
unit |
string |
'' |
No | Optional unit suffix appended to badge values. |
unlimitedValue |
number | null |
null |
No | Value at/above which the badge displays Unlimited. |
displayTransform |
((value: number) => number) | null |
null |
No | Optional transform applied to the displayed value (shown with toFixed(1)). |
<script> import RangeScale, { type Marker } from '$ui/form/RangeScale.svelte';
let markers: Marker[] = [{ id: 'min', label: 'Min', color: 'blue', value: 20 }];</script>
<RangeScale min={0} max={100} step={5} bind:markers unit="%" on:change={(e) => (markers = e.detail.markers)} />Events & Slots
Section titled “Events & Slots”on:change(untyped dispatcher) —dispatch('change', { index, value, markers })fires during drag when a marker value changes.
Variants & Notes
Section titled “Variants & Notes”orientation: horizontal or vertical;direction: start or end; seven marker colors (accent/blue/green/orange/red/purple/neutral).- Exports the
MarkerColortype andMarkerinterface fromcontext="module"— importtype { Marker }alongside the default import. - Neighbor min-separation is enforced only during drag, not on external updates (external values are only clamped to min/max). Supports mouse and touch dragging via window listeners.
KeyValueList
Section titled “KeyValueList”Dynamic key-value pair editor with add/remove rows. The value column can be plain text or a semantic-version (major.minor.patch) editor using NumberInput steppers. Responsive stacked-card (mobile) / grid (desktop) layout, with an optional locked first entry.
import KeyValueList from '$ui/form/KeyValueList.svelte';| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
value |
Record<string, string> |
{} |
No | Bindable object of key/value pairs. |
label |
string |
'' |
No | Section label. |
description |
string |
'' |
No | Helper text under the label. |
keyLabel |
string |
'Key' |
No | Header/label for the key column. |
valueLabel |
string |
'Value' |
No | Header/label for the value column. |
keyPlaceholder |
string |
'Enter key' |
No | Placeholder for key inputs. |
valuePlaceholder |
string |
'Enter value' |
No | Placeholder for value inputs (text mode). |
onchange |
((value: Record<string, string>) => void) | undefined |
undefined |
No | Callback fired when entries change (syncToValue). |
lockedFirst |
{ key: string; value?: string; minMajor?: number } | undefined |
undefined |
No | Pins a non-removable/non-renamable first entry; minMajor sets its version major floor. |
onLockedDeleteAttempt |
(() => void) | undefined |
undefined |
No | Callback when the user tries to delete the locked entry. |
onLockedEditAttempt |
(() => void) | undefined |
undefined |
No | Callback when the user focuses the locked entry key field. |
onLockedVersionMinBlocked |
(() => void) | undefined |
undefined |
No | Callback when the locked entry’s version major hits its min. |
valueType |
'text' | 'version' |
'text' |
No | Value editor type: free text or major.minor.patch version steppers. |
versionMinMajor |
number |
0 |
No | Minimum major version for non-locked version entries. |
addDisabled |
boolean |
false |
No | Disable the Add entry action (fires onAddBlocked). |
onAddBlocked |
(() => void) | undefined |
undefined |
No | Callback when Add is attempted while addDisabled. |
<script> import KeyValueList from '$ui/form/KeyValueList.svelte';
let demoKV = {};</script>
<KeyValueList bind:value={demoKV} label="Environment Variables" keyPlaceholder="Variable name" valuePlaceholder="Value" onchange={(v) => (demoKV = v)}/>Events & Slots
Section titled “Events & Slots”onchangecallback prop (nocreateEventDispatcher). Also fires the locked/add blocked callbacks.
Variants & Notes
Section titled “Variants & Notes”valueTypetext or version; optionallockedFirstentry; responsive mobile-card vs. desktop-grid layout.- Composes
FormInput(key + text value) andNumberInput(version parts). New version entries default to1.0.0. - Empty keys are dropped from the emitted object but preserved as in-progress editing rows. External value changes re-sync entries while keeping empty-key rows and re-pinning
lockedFirstto index 0.
MaskedApiKey
Section titled “MaskedApiKey”Read-only API key display with reveal/hide and copy-to-clipboard controls, an auto-hide timeout, transient status messaging (aria-live), and reveal/copy event callbacks.
import MaskedApiKey from '$ui/form/MaskedApiKey.svelte';| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
id |
string |
'masked-api-key' |
No | Base id; the status region uses ${id}-status. |
label |
string |
'API key' |
No | Label above the value and in button aria-labels. |
maskedValue |
string |
'' |
No | Masked representation shown when not revealed. |
value |
string |
'' |
No | Actual secret value (shown when revealed, used for copy). |
hasValue |
boolean |
false |
No | Whether a key exists (drives interactivity/labels). |
revealTimeoutMs |
number |
30000 |
No | Auto-hide delay after reveal; 0 disables auto-hide. |
copyFeedbackVisibleMs |
number |
2000 |
No | Duration the status feedback stays visible. |
disabled |
boolean |
false |
No | Disable reveal/copy interaction. |
revealLabel |
string |
'Reveal' |
No | Reveal button aria fragment. |
hideLabel |
string |
'Hide' |
No | Hide button aria fragment. |
copyLabel |
string |
'Copy' |
No | Copy button aria fragment. |
<script> import MaskedApiKey from '$ui/form/MaskedApiKey.svelte';
const apiKey = 'secret-value';
function log(detail) { // ... }</script>
<MaskedApiKey label="API key" value={apiKey} maskedValue="abc***xyz" hasValue on:revealChange={(e) => log(e.detail)} on:copyFeedback={(e) => log(e.detail)}/>Events & Slots
Section titled “Events & Slots”on:revealChange—{ revealed: boolean; reason: 'manual' | 'timeout' }.on:copyFeedback—{ success: boolean; message: string; error?: Error }.
Variants & Notes
Section titled “Variants & Notes”- With/without value (a
noValueLabelstate); reveal auto-hide vs. manual (revealTimeoutMs=0). - Copy uses
navigator.clipboard.writeText; it fails gracefully with an error status if unavailable or the value is empty. Timers are cleared on destroy, and it auto-hides if it becomes non-interactive while revealed. - Button label text is hardcoded (
Reveal/Hide/Copy) in the markup;revealLabel/hideLabel/copyLabeldrive thearia-label/titleonly.
CronInput
Section titled “CronInput”Human-friendly cron expression builder. Presents a schedule-type selector (Every/Hourly/Daily/Weekly/Monthly/Custom) with contextual controls, two-way syncing a standard 5-field cron string. Validates custom expressions with croner.
import CronInput from '$ui/cron/CronInput.svelte';| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
value |
string |
'0 * * * *' |
No | Bindable cron expression (5-field; 6-field input is tolerated by dropping the seconds field on parse). |
disabled |
boolean |
false |
No | Disable all sub-controls. |
<script> import CronInput from '$ui/cron/CronInput.svelte';
let cronExpression = '0 * * * *';</script>
<CronInput bind:value={cronExpression} />Events & Slots
Section titled “Events & Slots”- No dispatcher; communicates solely via the bindable
value(setValuemutatesvalue).
Variants & Notes
Section titled “Variants & Notes”- Schedule types: every (interval minutes 1-60), hourly (minute 0-59), daily (time), weekly (weekday + time), monthly (day 1-31 + time), custom (raw cron with validation).
- Composes
DropdownSelect,NumberInput,TimeInput, andFormInput. Parses an existing value back into the simplest matching schedule type (parseSimple) or falls back tocustom. - Custom mode validates via
new Cron(customCron)fromcronerand applies a red border on error (cronError).buildCronFromSimpleclamps ranges, and reactive blocks keepvalueand the UI in sync in both directions.
AdvancedSection
Section titled “AdvancedSection”Presentational disclosure wrapper: an always-visible default slot plus a collapsible advanced slot toggled by a Show/Hide Advanced button. Bindable basic/advanced mode with a slide transition and ARIA wiring.
import AdvancedSection from '$ui/form/AdvancedSection.svelte';| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
sectionId |
string |
'' |
No | Base id for panel/heading ARIA ids; falls back to an auto-generated id. |
sectionTitle |
string |
'Advanced settings' |
No | Section heading text. |
sectionHint |
string |
'These options are hidden by default and are optional.' |
No | Sub-heading hint text (rendered if set). |
showAdvancedLabel |
string |
'Show Advanced' |
No | Toggle button label when collapsed. |
hideAdvancedLabel |
string |
'Hide Advanced' |
No | Toggle button label when expanded. |
mode |
UiPreferenceMode ('basic' | 'advanced') |
'basic' |
No | Bindable disclosure mode; advanced reveals the advanced slot. |
<script> import AdvancedSection from '$ui/form/AdvancedSection.svelte'; import BasicFields from './BasicFields.svelte'; import AdvancedFields from './AdvancedFields.svelte';
let mode = 'basic';</script>
<AdvancedSection sectionTitle="Advanced settings" bind:mode> <BasicFields /> <svelte:fragment slot="advanced"><AdvancedFields /></svelte:fragment></AdvancedSection>Events & Slots
Section titled “Events & Slots”- No dispatcher; two-way state via
bind:mode. The internal button togglesmode. - Default slot — always-visible content;
advancedslot — collapsible content (falls back toNo advanced options availabletext).
Variants & Notes
Section titled “Variants & Notes”modebasic or advanced. Respectsprefers-reduced-motion(disables the slide).UiPreferenceModeis imported from$shared/disclosure/sectionKeys.ts. UsesActionsBarfor the toggle; a module-scope counter generates fallback ids.- This is the presentational primitive that
DisclosureSectionwraps with persistence.
DisclosureSection
Section titled “DisclosureSection”Stateful wrapper around AdvancedSection that persists basic/advanced mode per section key via the user-interface-preferences store and integrates with the complexity-tier context (tier-driven default mode plus activity recording).
import DisclosureSection from '$ui/form/DisclosureSection.svelte';| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
sectionKey |
SectionKey |
— | Yes | Persistence/store key identifying this section (from $shared/disclosure/sectionKeys.ts). |
sectionTitle |
string |
'Advanced settings' |
No | Section heading (passed to AdvancedSection). |
sectionHint |
string |
'These options are hidden by default and are optional.' |
No | Sub-heading hint (passed through). |
initialMode |
UiPreferenceMode | undefined |
undefined |
No | Explicit initial mode; when set it overrides tier-driven defaults and blocks tier/store overrides. |
showAdvancedLabel |
string |
'Show Advanced' |
No | Toggle button label when collapsed. |
hideAdvancedLabel |
string |
'Hide Advanced' |
No | Toggle button label when expanded. |
<script> import DisclosureSection from '$ui/form/DisclosureSection.svelte'; import BasicFields from './BasicFields.svelte'; import AdvancedFields from './AdvancedFields.svelte';
const mySectionKey = 'quality-profile.advanced';</script>
<DisclosureSection sectionKey={mySectionKey} sectionTitle="Advanced options"> <BasicFields /> <svelte:fragment slot="advanced"><AdvancedFields /></svelte:fragment></DisclosureSection>Events & Slots
Section titled “Events & Slots”- No dispatcher; persists mode changes to the section store and records complexity-tier activity on manual toggles.
- Default slot — always-visible content;
advancedslot — collapsible content (both forwarded toAdvancedSection).
Variants & Notes
Section titled “Variants & Notes”- Tier-driven default mode; explicit
initialModeoverride; persisted vs. unpersisted state. - Depends on
getComplexityTierContext(),getUserInterfacePreferenceSectionStore(), and pure helpers indisclosureSectionLogic.ts(resolveDisclosureInitialMode,resolveTierDrivenMode,shouldBlockTierUpdates). - Surfaces a warning alert if activity persistence fails (except
AuthRequiredError). Cleans up subscriptions and the store on destroy.SectionKey/UiPreferenceModecome from$shared/disclosure/sectionKeys.ts.