litefyr-dynamic-form-elements
v0.1.1
Published
Re-usable dynamic form elements for Svelte.
Readme
DynamicForm Presentation Components
These components provide the reusable UI layer of DynamicForm. They render markup and encapsulate UI behavior, while form state, server communication, and field validation are supplied by the surrounding form integration.
Import
All components are exported through index.ts:
import { Button, Pagination } from "./Presentation";A component can also be imported directly:
import Button from "./Presentation/Button.svelte";The components use Svelte 5 runes and are configured through $props().
Component Overview
| Component | Purpose |
| ------------------ | ------------------------------------------------------- |
| Button | Consistently styled button with snippet content |
| Dropzone | File selection, file previews, and file errors |
| Fieldset | Semantic field grouping with an optional legend |
| FormElement | Renderer for DynamicForm field types |
| GroupHeadline | Headline for field groups |
| LabelWrapper | Label and layout wrapper for fields |
| LoadingIndicator | Delayed spinner and loading indicator |
| Pagination | Previous/next/submit navigation for multi-page forms |
| RichTextEditor | Rich-text editor with toolbar and editor configuration |
| SendButton | Submit button with loading and success states |
| Textarea | Styled textarea with dynamic minimum and maximum height |
| Validation | Validation message and help-text output |
Button
Button renders a <button> with the project classes btn, btn--inline, and either btn--theme or btn--link.
Props
| Prop | Type | Default | Description |
| ---------- | --------------------------------- | ---------- | ------------------------------ |
| theme | "nested" \| "link" | "nested" | Selects the button variant |
| type | "button" \| "submit" \| "reset" | "button" | Native button type |
| before | unknown | "" | Content before the button text |
| after | unknown | "" | Content after the button text |
| children | Snippet | - | Button content |
Additional props such as disabled, name, value, and event handlers are forwarded to the native button.
<Button theme="link" onclick={cancel}>Cancel</Button>
<Button type="submit" disabled={sending}>Submit</Button>Dropzone
Dropzone is the presentation layer for file uploads. It manages accepted files, drag state, previews, file reading, and dropzone error messages. The surrounding form integration receives changes through callbacks.
Main Props
| Prop | Type | Description |
| ------------------- | --------------------------------------- | ------------------------------------------------------------------------------- |
| name | string | Field name |
| id | string | ID of the associated file input |
| field | Record<string, any> | Field definition, for example multiple, accept, maxSize, or previewSize |
| formSettings | { labels: Record<string, string> } | Labels for the dropzone and error messages |
| onValueChange | (value: File \| File[] \| "") => void | Called when files are selected or removed |
| onValidationError | (message: string \| false) => void | Receives the current dropzone error |
| onValidate | () => void | Called when the file dialog is cancelled |
<Dropzone
name="attachments"
id="attachments"
field={{ multiple: true, accept: ["image/png"], previewSize: 160 }}
{formSettings}
onValueChange={(value) => (formValues.attachments = value)}
onValidationError={(message) => (validationErrors.attachments = message)}
onValidate={validateAttachments}
/>Do not duplicate file and preview processing in the surrounding form integration.
Fieldset
Fieldset renders a native <fieldset> for grouping related form controls. It can render an optional legend and forwards additional attributes such as disabled, name, or aria-* to the native element. Child content is provided through a Svelte snippet.
| Prop | Type | Default | Description |
| ------------- | --------- | --------------------- | ------------------------------------ |
| legend | string | - | Optional fieldset legend |
| legendClass | string | "block text-fl-2xl" | Classes applied to the legend |
| class | string | - | Additional fieldset classes |
| children | Snippet | - | Content rendered inside the fieldset |
<Fieldset legend="Contact details">
<input name="email" type="email" />
</Fieldset>FormElement
FormElement renders a DynamicForm field based on its type, such as checkbox, checkboxGroup, radioGroup, select, textarea, rte, file, or a standard input.
The following example shows the complete contract between the presentation renderer and a stateful form integration:
<FormElement
name="email"
type="email"
label="Email"
required={true}
field={{ placeholder: "[email protected]" }}
formValues={{ email: value }}
validationMessages={[validationError]}
validate={handleInput}
validateWithOutput={handleBlur}
/>In a complete form, the integration supplies the current field value and the callbacks that connect validation and state updates.
FormElement Props
| Prop | Type | Description |
| -------------------- | ------------------------ | -------------------------------------------------- |
| name | string | Field name |
| type | string | Field type |
| field | Record<string, any> | Native field attributes and field-specific options |
| label | string | Field label |
| required | boolean | Required-field state |
| formValues | Record<string, any> | Bindable form values |
| validationMessages | Array<string \| false> | Messages passed to Validation |
| validate | (event: Event) => void | Inline validation callback |
| validateWithOutput | (event: Event) => void | Validation callback that outputs the result |
GroupHeadline
Renders a headline for checkbox and radio groups.
<GroupHeadline label="Interests" required={true} />| Prop | Type | Description |
| ---------- | --------- | ------------------------------- |
| label | string | Headline text |
| required | boolean | Shows the required-field marker |
LabelWrapper
Arranges a label, field content, and optional validation output. Checkbox and radio fields automatically use a different content order.
| Prop | Type | Description |
| ------------ | ------------------------------- | ------------------------------------------------- |
| label | string | Label text |
| required | boolean | Required-field marker |
| type | "checkbox" \| "radio" \| null | Affects layout and label position |
| group | boolean | Removes the normal top spacing for grouped fields |
| validation | Snippet | Optional validation output |
| class | string | Additional CSS class |
| cols | number | Number of grid columns |
| id | string | ID associated with the field |
| children | Snippet | Field content |
<LabelWrapper label="Email" required={true} id="email">
<input id="email" name="email" type="email" />
</LabelWrapper>LoadingIndicator
Shows a spinner after a delay, followed by animated dots. Nothing is rendered when showLoading={false}.
| Prop | Type | Default | Description |
| -------------- | --------- | ------- | ------------------------------------------------- |
| showLoading | boolean | true | Visibility |
| delayTime | number | 500 | Delay before showing the spinner, in milliseconds |
| timeoutTime | number | 5000 | Time before switching to the dots animation |
| size | number | 24 | SVG size |
| dynamicWidth | boolean | false | Animates the width between spinner and dots |
| title | string | null | HTML title |
| class | string | null | Additional CSS class |
<LoadingIndicator showLoading={loading} title="Loading" dynamicWidth={true} />Pagination
Pagination encapsulates multi-page form navigation. On the last page it automatically renders SendButton instead of the next button.
| Prop | Type | Description |
| ----------------- | ----------------------------------- | ---------------------------------------- |
| showPrevious | boolean | Shows the previous button |
| hasNextPage | boolean | Selects next or submit state |
| previousLabel | string | Previous-button label |
| nextLabel | string | Next-button label |
| submitLabel | string | Submit-button label |
| submittingLabel | string | Loading-indicator label |
| sending | boolean | Submit is in progress |
| submitted | boolean | Form was submitted successfully |
| allowResend | boolean | Allows submitting again |
| successMessage | string \| null | Success message |
| onPrevious | () => void | Callback for moving to the previous page |
| validateForm | () => boolean \| Promise<boolean> | Validation before moving or submitting |
RichTextEditor
RichTextEditor contains the toolbar, Typewriter editor, heading levels 2 through 6, Markdown input behavior, and HTML synchronization. It accepts an external value and reports changes through onValueChange.
| Prop | Type | Description |
| --------------- | ------------------------------------------------------------- | ---------------------------------- |
| value | formValue | Bindable HTML value |
| name | string | Field name |
| placeholder | string | Editor placeholder |
| id | string | Input ID |
| required | boolean | Required-field state |
| formLabels | Record<string, string> | Default ARIA labels from the form |
| ariaLabels | Partial<RteAriaLabels> | Overrides individual ARIA labels |
| onValueChange | (value: string) => void | Receives the normalized HTML value |
| validateFunc | (element?: HTMLInputElement) => boolean \| Promise<boolean> | Validation callback |
<RichTextEditor name="description" bind:value={description} formLabels={formSettings.labels} onValueChange={(value) => (formValues.description = value)} validateFunc={validateDescription} />SendButton
SendButton is the form submit control. It renders the submit button, loading indicator, and success message.
| Prop | Type | Description |
| ----------------- | ----------------------------------- | -------------------------------------- |
| label | string | Current button label |
| submittingLabel | string | Loading-indicator label |
| sending | boolean | Disables the button and shows loading |
| submitted | boolean | Prevents another submit unless allowed |
| allowResend | boolean | Allows submitting again |
| successMessage | string \| null | Success message |
| validateForm | () => boolean \| Promise<boolean> | Submit validation callback |
Textarea
Textarea is a thin wrapper around <textarea>. All unknown props are forwarded to the native element.
| Prop | Type | Default | Description |
| ----------- | -------- | ------- | ------------------------------- |
| value | string | "" | Bindable value |
| minHeight | number | 2 | Minimum line height used by CSS |
| maxHeight | number | 6 | Maximum line height used by CSS |
<Textarea bind:value={comment} rows={4} name="comment" />Validation
Renders validation messages and optional help text. Messages with the value false are ignored. Messages are rendered as HTML, so they must come from a trusted source.
| Prop | Type | Default | Description |
| ---------- | ------------------------ | ------- | ---------------------------- |
| messages | Array<string \| false> | [] | Validation messages |
| help | string | "" | Help text below the messages |
<Validation messages={[clientError, serverError]} help="Maximum 500 characters" />Architecture Rules
- Presentation components contain markup, layout, and UI-specific behavior.
- The stateful form integration owns form state, server communication, and validation functions.
- Form state crosses the boundary through props and callbacks.
- New Presentation components must not import internal files outside this directory.
- External UI libraries such as
svelte-dropzone-runesortypewriter-editorare allowed when the component renders their UI directly. - New public components must also be exported from
index.ts.
