Autosizing textarea
A labelled textarea that grows a line at a time from minRows to maxRows, then scrolls, with an optional footer for a count or actions inside the same frame. CSS field-sizing first, a measured fallback elsewhere.
cmp_autosize_textarea_01 Preview
Fit to the available width. The frame follows the height of its content; previews taller than the maximum auto-height scroll inside it.
Give this component to your coding agent Copy a prompt that fetches this exact version and palette through the PageSugar MCP server.
Using the PageSugar MCP server, fetch component cmp_autosize_textarea_01 version 1.0.0 with variant "blue", first inspect its requirements and license status and confirm this project uses Svelte 5 and Tailwind CSS 4. Retrieve every manifest file, including binary assets and any manifest-only response files, preserving relative paths. Then integrate the source and follow its usage notes. Run project checks, review the browser result and report anything unverified. Do not substitute another version or invent missing files.Not connected yet? Set up the MCP server
Code
- Palette
- Blue accent
- Version
- 1.0.0
- Digest
Full digest
sha256-0a454a788c36a65d0225d0cfc3d7910cab81f0577a2f87bb40c257bb486aa026
<!--
A textarea that grows a whole line at a time from minRows to maxRows, then scrolls. The text
and an optional footer (a count, a note, actions) share one framed shape and one focus edge, so
growth reads as the writing surface getting taller rather than a box pushing a toolbar down.
CSS field-sizing does the growing where the browser supports it; elsewhere a client effect
measures the text. It is a plain native <textarea name>, so it submits before any script runs.
-->
<script lang="ts" module>
export type AutosizeTextareaVariant = 'field' | 'composer';
</script>
<script lang="ts">
import type { Snippet } from 'svelte';
import type { HTMLTextareaAttributes } from 'svelte/elements';
type Passthrough = Omit<
HTMLTextareaAttributes,
'value' | 'id' | 'name' | 'rows' | 'class' | 'children' | 'aria-invalid'
>;
interface Props extends Passthrough {
/** Visible label. Always rendered; hideLabel keeps it for screen readers only. */
label: string;
/** Bindable value. Programmatic changes resize the field too. */
value?: string;
/** Form field name for native submission. */
name?: string;
/** Height in lines before any text is typed, and the server-rendered height. */
minRows?: number;
/** Height in lines at which the field stops growing and scrolls. */
maxRows?: number;
/** Help text under the label, linked with aria-describedby. */
hint?: string;
/** Validation message from the consumer. Sets aria-invalid; the component never validates. */
error?: string | null;
/** Read before the error by screen readers only, for other languages. */
errorPrefix?: string;
/** Content under the text inside the same frame: a count, a note, actions. */
footer?: Snippet;
/** 'field' is a form control with a 3:1 edge; 'composer' is a raised frame for comments and chat. */
variant?: AutosizeTextareaVariant;
/** Keep the label for screen readers only, for a composer whose placeholder says what to write. */
hideLabel?: boolean;
/** Textarea id. Defaults to one generated per instance. */
id?: string;
/** Extra classes on the outer wrapper, for placement. */
class?: string;
}
let {
label,
value = $bindable(),
name,
minRows = 2,
maxRows = 10,
hint,
error = null,
errorPrefix = 'Error:',
footer,
variant = 'field',
hideLabel = false,
id,
class: className,
...rest
}: Props = $props();
const uid = $props.id();
const inputId = $derived(id ?? `${uid}-input`);
const hintId = `${uid}-hint`;
const errorId = `${uid}-error`;
/* Whole lines only, at least one, and the cap never below the floor. */
const min = $derived(Math.max(1, Math.round(minRows) || 1));
const max = $derived(Math.max(min, Math.round(maxRows) || min));
const invalid = $derived(!!error);
/* Hint first, then the error, then anything the consumer attached (a character counter). */
const describedBy = $derived(
[
...new Set(
[
hint && hintId,
invalid && errorId,
...(rest['aria-describedby'] ?? '').split(/\s+/)
].filter(Boolean)
)
].join(' ') || undefined
);
let textarea: HTMLTextAreaElement | undefined = $state();
let shape: HTMLDivElement | undefined = $state();
let footerRow: HTMLDivElement | undefined = $state();
/* Browsers with field-sizing grow the field in CSS, before hydration and without measuring. */
const sizesInCss = () =>
typeof CSS !== 'undefined' &&
typeof CSS.supports === 'function' &&
CSS.supports('field-sizing', 'content');
/*
* The fallback measure. The shape is held at its current height while the textarea is reset
* to auto, so the page never sees the momentary collapse and cannot scroll-jump. The CSS
* min and max block sizes clamp the result to whole lines between minRows and maxRows.
*/
function fit() {
if (!textarea || !shape || sizesInCss()) return;
const held = shape.style.minHeight;
shape.style.minHeight = `${shape.offsetHeight}px`;
/* No scrollbar while measuring: a classic one would narrow the text and add wrapped lines. */
textarea.style.overflowY = 'hidden';
textarea.style.height = 'auto';
textarea.style.height = `${textarea.scrollHeight}px`;
textarea.style.overflowY = '';
shape.style.minHeight = held;
/* At the cap, typing at the end keeps the caret's line in view. */
const capped = textarea.scrollHeight > textarea.clientHeight;
const typingAtEnd =
textarea === document.activeElement && textarea.selectionEnd === textarea.value.length;
if (capped && typingAtEnd) textarea.scrollTop = textarea.scrollHeight;
}
/* Re-measure on every value change, typed or bound, and when the line limits change. */
$effect(() => {
void [value, min, max, variant];
fit();
});
/* A narrower column rewraps the text, so the fallback re-measures when the width changes. */
$effect(() => {
if (!textarea || sizesInCss() || typeof ResizeObserver === 'undefined') return;
let width = textarea.clientWidth;
const observer = new ResizeObserver(() => {
if (!textarea || textarea.clientWidth === width) return;
width = textarea.clientWidth;
fit();
});
observer.observe(textarea);
return () => observer.disconnect();
});
/*
* A press on the frame or the footer's empty space focuses the textarea, as a press inside one
* shape should. Presses on anything in the footer (a button, a link, a note someone may want to
* select) are left alone.
*/
function focusTextarea(event: PointerEvent) {
if (!textarea || !footerRow || event.button !== 0) return;
if (event.target !== event.currentTarget && event.target !== footerRow) return;
if (textarea.matches(':disabled')) return;
event.preventDefault();
textarea.focus();
}
</script>
<div
class={['autosize-textarea min-w-0', className]}
data-variant={variant}
data-invalid={invalid ? '' : undefined}
style:--_min-rows={min}
style:--_max-rows={max}
>
<!-- hideLabel hides the label alone; a hint stays visible, since it carries instructions. -->
<div class={['grid', (!hideLabel || hint) && 'gap-2']}>
<div class="min-w-0">
<label
for={inputId}
class={[
'block text-sm leading-5 font-medium text-pretty break-words text-(--_ink)',
hideLabel && 'sr-only'
]}
>
{label}
</label>
{#if hint}
<p
id={hintId}
class={[
'text-[13px] leading-5 text-pretty break-words text-(--_muted)',
!hideLabel && 'mt-1'
]}
>
{hint}
</p>
{/if}
</div>
<div class="min-w-0">
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
bind:this={shape}
class={[
'autosize-textarea__shape flex flex-col bg-(--_fill) text-(--_ink)',
variant === 'field'
? 'rounded-xl border border-(--_edge)'
: 'rounded-2xl shadow-(--_shadow-composer)'
]}
onpointerdown={focusTextarea}
>
<textarea
bind:this={textarea}
{...rest}
bind:value
{name}
rows={min}
id={inputId}
aria-describedby={describedBy}
aria-invalid={invalid ? 'true' : undefined}
class={[
'autosize-textarea__input block w-full min-w-0 resize-none outline-none',
'py-(--_pad-y) text-sm leading-6 text-(--_ink) pointer-coarse:text-base',
'break-words placeholder:text-(--_placeholder) disabled:cursor-not-allowed',
variant === 'field' ? 'px-3' : 'px-4'
]}></textarea>
{#if footer}
<div
bind:this={footerRow}
class={[
'autosize-textarea__footer flex min-w-0 flex-wrap items-center justify-between gap-2',
'text-[13px] leading-5 text-(--_muted)',
variant === 'field' ? 'px-1 pb-1' : 'px-2 pb-2'
]}
>
{@render footer()}
</div>
{/if}
</div>
{#if invalid}
<p id={errorId} class="mt-1 flex items-start gap-1 text-[13px] leading-5 text-(--_danger)">
<span class="grid h-lh shrink-0 place-items-center" aria-hidden="true">
<svg class="size-3.5" viewBox="0 0 16 16" fill="none">
<circle cx="8" cy="8" r="6.25" stroke="currentColor" stroke-width="1.75" />
<path
d="M8 4.75v3.5"
stroke="currentColor"
stroke-width="1.75"
stroke-linecap="round"
/>
<circle cx="8" cy="11" r="1" fill="currentColor" />
</svg>
</span>
<span class="min-w-0 text-pretty break-words"
><span class="sr-only">{`${errorPrefix} `}</span>{error}</span
>
</p>
{/if}
</div>
</div>
</div>
<style>
/* Public tokens: set --autosize-textarea-* on the field or any ancestor to retone it. */
.autosize-textarea {
--_accent: var(--autosize-textarea-accent, #1d4ed8);
--_danger: var(--autosize-textarea-danger, #b91c1c);
--_ink: var(--autosize-textarea-ink, #18181b);
--_muted: var(--autosize-textarea-muted, #52525b);
--_placeholder: var(--autosize-textarea-placeholder, #71717a);
/* 3:1 against white: in the field variant the edge is what marks an empty field (WCAG 1.4.11). */
--_hairline: var(--autosize-textarea-hairline, rgb(0 0 0 / 0.44));
--_surface: var(--autosize-textarea-surface, #ffffff);
/* Not used by the field itself: footer actions you pass read it as the text on an accent fill. */
--_on-accent: var(--autosize-textarea-on-accent, #ffffff);
--_edge: var(--_hairline);
--_ring: var(--_accent);
/* The frame's fill; read-only and disabled raise it. */
--_fill: var(--_surface);
/* Block padding of the text, per variant; the height formulas below add it to the lines. */
--_pad-y: 0.5rem;
/* Scroll shadows at the cap: their tone, and the covers that hide them at either end. */
--_scroll-shade: color-mix(in oklab, var(--_ink) 10%, transparent);
--_cover: calc(var(--_pad-y) + 0.75rem);
/*
* The composer's one elevation: the same 3:1 edge as the field (it still marks a control,
* WCAG 1.4.11), a contact shadow and a soft lift, lit from above.
*/
--_shadow-composer:
0 0 0 1px var(--_hairline), 0 1px 2px rgb(0 0 0 / 0.05), 0 12px 40px rgb(0 0 0 / 0.08);
}
.autosize-textarea[data-variant='composer'] {
--_pad-y: 0.75rem;
}
.autosize-textarea[data-invalid] {
--_edge: var(--_danger);
--_ring: var(--_danger);
}
/*
* Sizing. Heights are whole lines (1lh) plus the block padding, so they follow the text size
* and the page zoom. rows={minRows} sets the same floor before any CSS loads. field-sizing grows
* the field to its content where it is supported; the script's measure covers the rest. Below
* the cap the text never scrolls, so no scrollbar flickers in during growth.
*
* At the cap, text that scrolls into the padding fades out instead of being cut, and a soft
* shadow sits on any edge with text beyond it, so the overflow reads as more to come even with
* overlay scrollbars hidden at rest. The shadows are fixed to the viewport (scroll); fill-coloured
* covers ride on the content's ends (local) and hide them once you reach that end. Below the cap
* the covers always sit over the shadows and the padding holds no text, so nothing shows.
*/
.autosize-textarea__input {
min-block-size: calc(var(--_min-rows) * 1lh + 2 * var(--_pad-y));
max-block-size: calc(var(--_max-rows) * 1lh + 2 * var(--_pad-y));
overflow-y: auto;
scrollbar-width: thin;
background:
linear-gradient(var(--_fill), var(--_fill)) top / 100% var(--_cover) no-repeat local,
linear-gradient(var(--_fill), var(--_fill)) bottom / 100% var(--_cover) no-repeat local,
linear-gradient(var(--_scroll-shade), transparent) 0 var(--_pad-y) / 100% 0.75rem no-repeat
scroll,
linear-gradient(transparent, var(--_scroll-shade)) 0 calc(100% - var(--_pad-y)) / 100% 0.75rem
no-repeat scroll;
mask-image: linear-gradient(
transparent,
#000 var(--_pad-y),
#000 calc(100% - var(--_pad-y)),
transparent
);
}
@supports (field-sizing: content) {
.autosize-textarea__input {
field-sizing: content;
}
}
/* The edge and halo follow the textarea inside, so their transition lives here with them. */
.autosize-textarea__shape {
transition-property: border-color, box-shadow, background-color;
transition-duration: 150ms;
transition-timing-function: cubic-bezier(0.2, 0, 0, 1);
}
@media (prefers-reduced-motion: reduce) {
.autosize-textarea__shape {
transition-duration: 0s;
}
}
/* Hover only where typing is possible: one step darker edge on the field. */
@media (hover: hover) {
.autosize-textarea[data-variant='field']:not([data-invalid])
.autosize-textarea__shape:hover:not(:has(textarea:disabled, textarea[readonly])) {
--_edge: color-mix(in oklab, var(--_hairline), var(--_ink) 25%);
}
}
/*
* Focus: the field draws a two-pixel edge (border plus one ring) with a soft halo; the composer
* swaps its one-pixel ring for a two-pixel accent ring with the same halo and keeps its lift.
* Both follow :focus-visible on the textarea, which every keyboard and pointer focus of a
* text control matches.
*/
.autosize-textarea[data-variant='field'] .autosize-textarea__shape:has(textarea:focus-visible) {
border-color: var(--_ring);
box-shadow:
0 0 0 1px var(--_ring),
0 0 0 4px color-mix(in oklab, var(--_ring) 14%, transparent);
}
.autosize-textarea[data-variant='composer']
.autosize-textarea__shape:has(textarea:focus-visible) {
box-shadow:
0 0 0 2px var(--_ring),
0 0 0 6px color-mix(in oklab, var(--_ring) 12%, transparent),
0 12px 40px rgb(0 0 0 / 0.08);
}
.autosize-textarea[data-variant='composer'][data-invalid] .autosize-textarea__shape {
box-shadow:
0 0 0 1px var(--_danger),
0 1px 2px rgb(0 0 0 / 0.05),
0 12px 40px rgb(0 0 0 / 0.08);
}
/* Read-only: a raised fill, full-strength text, still focusable and selectable. */
.autosize-textarea__shape:has(textarea[readonly]) {
--_fill: color-mix(in oklab, var(--_surface), var(--_ink) 4%);
}
/* Disabled: raised fill, faded, and not interactive. */
.autosize-textarea__shape:has(textarea:disabled) {
--_fill: color-mix(in oklab, var(--_surface), var(--_ink) 4%);
opacity: 0.55;
cursor: not-allowed;
}
/* Forced colours drop shadows and repaint borders: the composer gets a real edge, focus an outline. */
@media (forced-colors: active) {
.autosize-textarea[data-variant='composer'] .autosize-textarea__shape {
outline: 1px solid CanvasText;
}
/* Prefixed with the root so it outranks the composer's resting outline above. */
.autosize-textarea .autosize-textarea__shape:has(textarea:focus-visible) {
outline: 2px solid Highlight;
outline-offset: 2px;
}
}
</style>
Usage#
On this pageGive it a label and a name and it renders a native textarea that submits with its form. It starts at minRows lines and grows with the text to maxRows, then scrolls. Bind value, pass a hint, set error when your own validation fails, and pass a footer snippet for a count or actions. It does not submit on Enter, limit length, validate, or format text.
- Suggested location
src/lib/components/autosize-textarea-01- Required props
label
Limitations
- Enter inserts a new line; it never submits. Add your own keydown handler for send-on-Enter, and check
event.isComposingso it does not interrupt IME input. - It does not count or limit length. Pass maxlength through, and pair it with a character counter in the footer.
- In browsers without field-sizing, sizing waits for hydration: an empty field is already the right height, but a prefilled value grows once the script runs.
- The fallback measures when the value or the field's width changes. A web font that loads late with different metrics is caught on the next keystroke, not immediately.
- No manual resize handle; the field sizes itself.
- The footer is your markup. Inside the field variant (12 px frame, 4 px footer inset) give controls an 8 px radius; inside the composer (16 px frame, 8 px inset) also 8 px.
Example
<script lang="ts">
import AutosizeTextarea from '$lib/components/autosize-textarea-01/AutosizeTextarea.svelte';
let comment = $state('');
</script>
<form method="POST" class="max-w-lg">
<AutosizeTextarea
label="Add a comment"
name="comment"
minRows={2}
maxRows={8}
placeholder="Write a comment"
bind:value={comment}
>
{#snippet footer()}
<span class="ps-2 text-[13px] text-zinc-600">Visible to your team</span>
<button
type="submit"
class="h-8 rounded-lg bg-zinc-900 px-3 text-[13px] font-medium text-white hover:bg-zinc-700"
>
Comment
</button>
{/snippet}
</AutosizeTextarea>
</form>The source imports its own files by relative path only. Examples import from $lib/...; SvelteKit 3 projects have no $lib alias by default, so import from your #lib subpath (with the .svelte extension) or a relative path instead.
Autosizing textarea#
A textarea that starts at minRows lines, grows a line at a time as the text gets longer, and
scrolls once it reaches maxRows. The text and an optional footer share one frame, so a count
or a Comment button rides along under the text as it grows.
<AutosizeTextarea label="Add a comment" name="comment" minRows={2} maxRows={8} bind:value>
{#snippet footer()}
<span class="ps-2 text-[13px] text-zinc-600">Visible to your team</span>
<button type="submit" class="h-8 rounded-lg bg-zinc-900 px-3 text-[13px] text-white">
Comment
</button>
{/snippet}
</AutosizeTextarea>How it sizes#
- Browsers with
field-sizing: content(Chrome and Edge 123+, Safari 26.2+, Firefox 152+) do the growing in CSS. A prefilled value is the right height in the server-rendered HTML, before any script runs. - Elsewhere a client effect measures the text whenever the value changes, whether typed or set from code, and whenever the field's width changes. It holds the frame's height while it measures, so the page below never jumps.
- Both paths share the same floor and cap in CSS:
minRowsandmaxRowslines of1lhplus the padding. The server rendersrows={minRows}, so an empty field never shifts when the script starts.
Field or composer#
variant="field" (the default) is a form control: a 3:1 edge, a 12 px radius, a two-pixel focus
edge. Use it in forms and comment boxes.
variant="composer" drops the field edge for a raised 16 px frame with a hairline ring and one
soft shadow, for the reply box at the foot of a thread. Pair it with hideLabel and a
placeholder that says who will read the reply, and put the send action last in the footer.
What it leaves to you#
- Enter. It inserts a line break. For send-on-Enter, add a
keydownhandler that skipsevent.isComposingand Shift+Enter, and say so near the field. - Length. Pass
maxlengththrough and put a counter in the footer. - Validation. Set
errorfrom your own checks; clear it withnull.
Props and content inputs#
On this page| Name | Type | Required | Default | Description |
|---|---|---|---|---|
label | string | Yes | None | Visible label. hideLabel keeps it for screen readers only. |
value | string | No | None | Bindable value. Setting it from code resizes the field, as typing does. |
name | string | No | None | Form field name, so native submission includes the field. |
minRows | number | No | 2 | Height in lines before anything is typed. Also the server-rendered height. Rounded to a whole line, at least 1. |
maxRows | number | No | 10 | Height in lines at which the field stops growing and scrolls. Never below minRows. |
hint | string | No | None | Help text under the label, linked first in aria-describedby. |
error | string | null | No | null | Validation message from your code. When set, the frame and focus ring turn to the danger colour, the message shows with an icon, and aria-invalid is set. |
errorPrefix | string | No | 'Error:' | Visually hidden text read before the error. |
footer | Snippet | No | None | Content under the text inside the same frame: a count, a note, actions. Laid out as a wrapping row with its first and last items at either end. |
variant | 'field' | 'composer' | No | 'field' | 'field' is a form control with a 3:1 border and a 12 px radius. 'composer' is a raised 16 px frame with a one-pixel 3:1 ring and one soft shadow, for comment threads and chat. |
hideLabel | boolean | No | false | Keep the label for screen readers only; a hint stays visible. Use it for a composer whose surroundings and placeholder make the purpose obvious. |
id | string | No | None | Textarea id. Defaults to one generated per instance. The hint and error always take generated ids. |
class | string | No | None | Extra classes on the outer wrapper, for placement. |
Customization#
On this pageChange the copy, line limits and footer through props, and retone the field through eight --autosize-textarea-* CSS variables. Hover, read-only and the focus halo are mixed from those, so they follow any retone.
- Line limits:
minRowsis the empty height andmaxRowsthe cap. Comment boxes suit 2 to 8, composers 1 to 6, long notes 4 to 12. The text is 14 px on a 24 px line (16 px on touch), so each row is 24 px. - Accent:
--autosize-textarea-accentcolours the focus edge and its halo, and--autosize-textarea-on-accentis the text on it. The field paints no accent fill itself; footer actions you pass can read both as var(--_accent) and var(--_on-accent), so one retone moves the Comment or Send button with the focus ring. Keep the accent at 3:1 against the page. - Danger:
--autosize-textarea-dangercolours the error edge, the error focus ring, the icon and the message. The default isred-700(6.5:1 on white). - Neutrals:
--autosize-textarea-inkis the label and text,--autosize-textarea-mutedthe hint and footer text,--autosize-textarea-placeholderthe placeholder,--autosize-textarea-hairlinethe field's resting edge (keep it at 3:1, it marks an empty field) and--autosize-textarea-surfacethe fill. - Dark band (
#09090b):--autosize-textarea-accent:#fafafa;--autosize-textarea-on-accent:#18181b;--autosize-textarea-ink:#fafafa;--autosize-textarea-muted:#a1a1aa;--autosize-textarea-placeholder:#a1a1aa;--autosize-textarea-hairline: rgb(255 255 255 / 0.35);--autosize-textarea-surface:#18181b;--autosize-textarea-danger:#f87171. Add color-scheme: dark on the band. - Cream page such as a bakery's
#fbf7f0: leave the hairline alone (it is alpha black, so it tints with the page) and set--autosize-textarea-surface:#fffdf9. - Footer: two children sit at either end; put a note first and the action last, and give the action margin-inline-start: auto so it stays at the end when a narrow column wraps it onto its own row. Start footer text with
ps-2in the field variant so it lines up with the typed text. Footer controls take an 8 px radius in both variants. - Composer: use variant 'composer' with
hideLabeland a placeholder that names who will read the reply, and put the send action last in the footer. It is the one raised surface in its area; do not put it inside another shadowed card. - Layout: the field fills its container. Stack fields with a 20 to 24 px gap and pass class for placement.
Public CSS variables
| Variable | Token |
|---|---|
--autosize-textarea-accent | accent |
--autosize-textarea-on-accent | onAccent |
--autosize-textarea-danger | danger |
--autosize-textarea-ink | ink |
--autosize-textarea-muted | muted |
--autosize-textarea-placeholder | placeholder |
--autosize-textarea-hairline | hairline |
--autosize-textarea-surface | surface |
Accessibility#
On this page- A native <label for> names the textarea; with
hideLabelit stays in the DOM as screen-reader text. Ids come from$props.id()or your id prop; the hint and error ids are always generated per instance. aria-describedbylists the hint id, then the error id, then anyaria-describedbyyou pass (a counter, for example), each only when present.aria-invalidis set only while error is set.- Growth changes nothing about semantics: it is still a native textarea, nothing is announced as it grows, focus never moves, and the page does not scroll except to keep the caret in view, as the browser does for any field.
- Heights are in line units (lh), so the field grows with browser text size and zoom. Text is 16 px on touch screens so iOS does not zoom.
- Enter inserts a line break, as in any textarea. If you add send-on-Enter, say so near the field and skip it while
event.isComposingis true. - The error is never colour alone: icon, text and a visually hidden
errorPrefix. It is not a live region; move focus or announce a summary when a submit fails. - Both variants mark the control with a 3:1 edge (WCAG 1.4.11): the field with its border, the composer with a one-pixel ring of the same hairline under its shadow. In forced-colours mode the composer gets a real outline.
- A capped field fades the line that scrolls into its padding rather than cutting it, so the overflow is visible even with overlay scrollbars that hide at rest.
- Footer content is yours: label icon-only buttons, and keep touch targets at 44 px on coarse pointers.
Release details#
On this page- Integration
- Local interaction
- Works without client-side JavaScript
- Server-side rendering supported
- Dependencies
- No additional runtime packages beyond Svelte and Tailwind CSS
- License
MIT. Default license approval is pending; see the license status before adopting the source.
- Version history
- 1.0.0 (Published) Current release · 22 October 2026
Only the current release is available. Keep downloaded source and its receipt if you need to use it again later.