Textarea Autosize
stimeo--textarea-autosize
A textarea that grows with what you type, between the row counts you set.
Grows a text field to fit what you type, between a minimum and a maximum number of rows, so it never runs away down the page. At the maximum it switches to scrolling inside itself, and CSS can tell that it has. It also publishes roughly how many rows there are, ready for anything that depends on it. The height changing never moves focus or the caret. Each change is reported, which helps if something else has to line up with it.
Keyboard
This component has no keyboard interactions of its own.
<%# Textarea autosize demo: type and the field grows to fit, up to maxRows (then it
scrolls). The controller sets the element's inline height from scrollHeight and
toggles data-at-max-rows at the cap; it listens for input itself, so no consumer
JS is needed. This demo only sets the textarea's base look. %>
<div class="textarea-autosize-demo">
<label class="textarea-autosize-demo__label" for="ta-autosize">
<%= t("components.textarea_autosize.demo.label") %>
</label>
<textarea
id="ta-autosize"
class="textarea-autosize-demo__field"
rows="2"
placeholder="<%= t('components.textarea_autosize.demo.placeholder') %>"
data-controller="stimeo--textarea-autosize"
data-stimeo--textarea-autosize-max-rows-value="8"></textarea>
</div>
/*
* Presentation-only styles for the textarea autosize demo.
* The library owns the textarea's height (set inline from scrollHeight) and toggles
* data-at-max-rows; this CSS only provides the base look and the line metrics the
* controller measures against. resize:none because the library manages height.
*/
.textarea-autosize-demo {
display: flex;
flex-direction: column;
gap: 0.5rem;
max-width: 32rem;
}
.textarea-autosize-demo__label {
font-weight: 600;
color: var(--fg);
}
.textarea-autosize-demo__field {
width: 100%;
padding: 0.5rem;
border: 1px solid var(--border);
border-radius: 0.375rem;
font: inherit;
line-height: 1.5;
resize: none;
overflow: hidden;
}
This demo needs no consumer-side JS (the controller handles the behavior).
These demo styles use shared design tokens (light + dark). Copy the shared styles too, then toggle data-theme on your root element for dark mode.
The data-* attributes you add to your own HTML to wire this component. Put the data-controller below on a root element, then place its targets / values / actions inside that element.
On the root element
data-controller="stimeo--textarea-autosize"
Values
| Name | Description | Attribute |
|---|---|---|
minRows
|
Minimum number of rows (default 1). | data-stimeo--textarea-autosize-min-rows-value |
maxRows
|
Maximum number of rows; 0 means unlimited (default 0). | data-stimeo--textarea-autosize-max-rows-value |
Actions
| Name | Action |
|---|---|
resize
|
stimeo--textarea-autosize#resize |
Events
| Name | Description | Event |
|---|---|---|
resize
|
Fires when the height changes, with detail.height / detail.rows. |
stimeo--textarea-autosize:resize |
State hooks
The library only manages these ARIA/data attributes and custom properties. Your CSS reads them to render the look — selectors like [aria-selected], [aria-expanded], or var(--stimeo--…) hook into this state.
| Hook | Target | Meaning |
|---|---|---|
data-at-max-rows |
Textarea | Present once the content reaches maxRows (the field then scrolls). |
--stimeo--textarea-rows |
Textarea | The current approximate row count. |