Input Group
Thymeleaf source
<sl:input-group> <sl:input-group-input type="search" name="q" placeholder="Search..." aria-label="Search"/> <sl:input-group-addon><sl:icon name="search"/></sl:input-group-addon> <sl:input-group-addon align="inline-end">12 results</sl:input-group-addon></sl:input-group>Rendered HTML
<div class="input-group" role="group"> <input class="input-group-input" type="search" name="q" placeholder="Search..." aria-label="Search"> <div class="input-group-addon" role="group"> <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"> <path d="m21 21-4.34-4.34"></path> <circle cx="11" cy="11" r="8"></circle> </svg> </div> <div class="input-group-addon" role="group" data-align="inline-end">12 results</div></div>Composition
Section titled “Composition”sl:input-group├── sl:input-group-input or sl:input-group-textarea└── sl:input-group-addon ├── sl:input-group-text ├── sl:input-group-button ├── sl:icon └── sl:kbdThe group draws the border, and its control has none of its own: the group shows the control’s focus ring, its error and its read-only look. A disabled control fades the whole group. A click on an addon, outside a button or link in it, focuses the control.
Write the addons after the control, so the control comes first in the tab order and is read before them; align
on the addon places it. inline-start, the default, puts it before the control on its line:
Thymeleaf source
<sl:input-group> <sl:input-group-input name="query" placeholder="Search..." aria-label="Search"/> <sl:input-group-addon><sl:icon name="search"/></sl:input-group-addon></sl:input-group>Rendered HTML
<div class="input-group" role="group"> <input class="input-group-input" type="text" name="query" placeholder="Search..." aria-label="Search"> <div class="input-group-addon" role="group"> <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"> <path d="m21 21-4.34-4.34"></path> <circle cx="11" cy="11" r="8"></circle> </svg> </div></div>inline-end
Section titled “inline-end”Thymeleaf source
<sl:input-group> <sl:input-group-input type="password" name="apiKey" placeholder="Enter password" aria-label="Password"/> <sl:input-group-addon align="inline-end"><sl:icon name="eye-off"/></sl:input-group-addon></sl:input-group>Rendered HTML
<div class="input-group" role="group"> <input class="input-group-input" type="password" name="apiKey" placeholder="Enter password" aria-label="Password"> <div class="input-group-addon" role="group" data-align="inline-end"> <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"> <path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"></path> <path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"></path> <path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"></path> <path d="m2 2 20 20"></path> </svg> </div></div>block-start
Section titled “block-start”block-start puts the addon above the control, across the whole group. border draws a line between the addon and
the control. In such a row an sl:input-group-text takes the free space, so what follows it sits at the end.
Thymeleaf source
<sl:input-group> <sl:input-group-input id="input-group-full-name" name="fullName" placeholder="Jane Doe"/> <sl:input-group-addon align="block-start"> <sl:label for="input-group-full-name">Full name</sl:label> </sl:input-group-addon></sl:input-group><sl:input-group> <sl:input-group-textarea name="script" rows="3" aria-label="Script">console.log('Hello, world!');</sl:input-group-textarea> <sl:input-group-addon align="block-start" border> <sl:input-group-text><sl:icon name="file-code"/> script.js</sl:input-group-text> <sl:input-group-button size="icon-xs" aria-label="Copy"><sl:icon name="copy"/></sl:input-group-button> </sl:input-group-addon></sl:input-group>Rendered HTML
<div class="input-group" role="group"> <input class="input-group-input" type="text" id="input-group-full-name" name="fullName" placeholder="Jane Doe"> <div class="input-group-addon" role="group" data-align="block-start"> <label class="label" for="input-group-full-name">Full name</label> </div></div><div class="input-group" role="group"> <textarea class="input-group-textarea" name="script" rows="3" aria-label="Script">console.log('Hello, world!');</textarea> <div class="input-group-addon" role="group" data-align="block-start" data-border="bottom"> <span class="input-group-text"> <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"> <path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"></path> <path d="M14 2v5a1 1 0 0 0 1 1h5"></path> <path d="M10 12.5 8 15l2 2.5"></path> <path d="m14 12.5 2 2.5-2 2.5"></path> </svg> script.js </span> <button class="button input-group-button" type="button" data-variant="ghost" data-size="icon-xs" aria-label="Copy"> <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"> <rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect> <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path> </svg> </button> </div></div>block-end
Section titled “block-end”Thymeleaf source
<sl:input-group> <sl:input-group-input name="price" placeholder="Enter amount" aria-label="Amount"/> <sl:input-group-addon align="block-end"> <sl:input-group-text>USD</sl:input-group-text> </sl:input-group-addon></sl:input-group><sl:input-group> <sl:input-group-textarea name="comment" rows="3" placeholder="Write a comment..." aria-label="Comment"/> <sl:input-group-addon align="block-end" border> <sl:input-group-text>0/280 characters</sl:input-group-text> <sl:input-group-button variant="default" size="sm" type="submit">Post</sl:input-group-button> </sl:input-group-addon></sl:input-group>Rendered HTML
<div class="input-group" role="group"> <input class="input-group-input" type="text" name="price" placeholder="Enter amount" aria-label="Amount"> <div class="input-group-addon" role="group" data-align="block-end"> <span class="input-group-text">USD</span> </div></div><div class="input-group" role="group"> <textarea class="input-group-textarea" name="comment" rows="3" placeholder="Write a comment..." aria-label="Comment"></textarea> <div class="input-group-addon" role="group" data-align="block-end" data-border="top"> <span class="input-group-text">0/280 characters</span> <button class="button input-group-button" type="submit" data-size="sm">Post</button> </div></div>sl:input-group-text holds muted text: a currency, a unit, a domain. The input still needs a name of its own, as the
text is not its label: aria-label="Amount in US dollars".
Thymeleaf source
<sl:input-group> <sl:input-group-input type="number" name="amount" placeholder="0.00" aria-label="Amount in US dollars"/> <sl:input-group-addon><sl:input-group-text>$</sl:input-group-text></sl:input-group-addon> <sl:input-group-addon align="inline-end"><sl:input-group-text>USD</sl:input-group-text></sl:input-group-addon></sl:input-group><sl:input-group> <sl:input-group-input name="website" placeholder="example" aria-label="Website"/> <sl:input-group-addon><sl:input-group-text>https://</sl:input-group-text></sl:input-group-addon> <sl:input-group-addon align="inline-end"><sl:input-group-text>.com</sl:input-group-text></sl:input-group-addon></sl:input-group><sl:input-group> <sl:input-group-input type="number" name="weight" aria-label="Weight in kilograms"/> <sl:input-group-addon align="inline-end"><sl:input-group-text>kg</sl:input-group-text></sl:input-group-addon></sl:input-group>Rendered HTML
<div class="input-group" role="group"> <input class="input-group-input" type="number" name="amount" placeholder="0.00" aria-label="Amount in US dollars"> <div class="input-group-addon" role="group"> <span class="input-group-text">$</span> </div> <div class="input-group-addon" role="group" data-align="inline-end"> <span class="input-group-text">USD</span> </div></div><div class="input-group" role="group"> <input class="input-group-input" type="text" name="website" placeholder="example" aria-label="Website"> <div class="input-group-addon" role="group"> <span class="input-group-text">https://</span> </div> <div class="input-group-addon" role="group" data-align="inline-end"> <span class="input-group-text">.com</span> </div></div><div class="input-group" role="group"> <input class="input-group-input" type="number" name="weight" aria-label="Weight in kilograms"> <div class="input-group-addon" role="group" data-align="inline-end"> <span class="input-group-text">kg</span> </div></div>Button
Section titled “Button”sl:input-group-button is an sl:button, ghost and xs by default, with sizes that fit inside the group: xs,
sm, icon-xs and icon-sm. An icon button needs an aria-label. As every sl:button, it does not submit unless
you write type="submit".
Thymeleaf source
<sl:input-group> <sl:input-group-input name="shareUrl" value="https://shadleaf.dev" readonly aria-label="Link"/> <sl:input-group-addon align="inline-end"> <sl:input-group-button size="icon-xs" aria-label="Copy"><sl:icon name="copy"/></sl:input-group-button> </sl:input-group-addon></sl:input-group><sl:input-group> <sl:input-group-input type="search" name="docsSearch" placeholder="Type to search..." aria-label="Search the docs"/> <sl:input-group-addon align="inline-end"> <sl:input-group-button variant="secondary" type="submit">Search</sl:input-group-button> </sl:input-group-addon></sl:input-group>Rendered HTML
<div class="input-group" role="group"> <input class="input-group-input" type="text" name="shareUrl" value="https://shadleaf.dev" readonly aria-label="Link"> <div class="input-group-addon" role="group" data-align="inline-end"> <button class="button input-group-button" type="button" data-variant="ghost" data-size="icon-xs" aria-label="Copy"> <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"> <rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect> <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path> </svg> </button> </div></div><div class="input-group" role="group"> <input class="input-group-input" type="search" name="docsSearch" placeholder="Type to search..." aria-label="Search the docs"> <div class="input-group-addon" role="group" data-align="inline-end"> <button class="button input-group-button" type="submit" data-variant="secondary" data-size="xs">Search</button> </div></div>The key is decoration: say the shortcut with aria-keyshortcuts on the input, and hide the addon from screen readers.
Thymeleaf source
<sl:input-group> <sl:input-group-input type="search" name="commandSearch" placeholder="Search..." aria-label="Search" aria-keyshortcuts="Control+K"/> <sl:input-group-addon><sl:icon name="search"/></sl:input-group-addon> <sl:input-group-addon align="inline-end" aria-hidden="true"><sl:kbd>⌘K</sl:kbd></sl:input-group-addon></sl:input-group>Rendered HTML
<div class="input-group" role="group"> <input class="input-group-input" type="search" name="commandSearch" placeholder="Search..." aria-label="Search" aria-keyshortcuts="Control+K"> <div class="input-group-addon" role="group"> <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"> <path d="m21 21-4.34-4.34"></path> <circle cx="11" cy="11" r="8"></circle> </svg> </div> <div class="input-group-addon" role="group" data-align="inline-end" aria-hidden="true"> <kbd class="kbd">⌘K</kbd> </div></div>Loading
Section titled “Loading”loading on the button shows a spinner, disables it and announces the busy state.
Thymeleaf source
<sl:input-group> <sl:input-group-input name="username" value="wim" aria-label="Username"/> <sl:input-group-addon align="inline-end"> <sl:input-group-button loading>Check</sl:input-group-button> </sl:input-group-addon></sl:input-group>Rendered HTML
<div class="input-group" role="group"> <input class="input-group-input" type="text" name="username" value="wim" aria-label="Username"> <div class="input-group-addon" role="group" data-align="inline-end"> <button class="button input-group-button" type="button" data-variant="ghost" data-size="xs" aria-busy="true" disabled> <span class="button-icon" data-icon="inline-start"> <svg xmlns="http://www.w3.org/2000/svg" class="icon button-spinner" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"> <path d="M21 12a9 9 0 1 1-6.219-8.56"></path> </svg> </span> <span class="sl-sr-only">Loading</span> Check </button> </div></div>In a field
Section titled “In a field”Put th:field on the field, as for an input. The sl:input-group-input (or
sl:input-group-textarea) inside the group takes it: Spring sets its id, name and value, the label points at it,
and it is described by the field’s description and error.
<form th:object="${order}" th:action="@{/orders}" method="post"> <sl:field th:field="*{budget}"> <sl:field-label>Budget</sl:field-label> <sl:input-group> <sl:input-group-input type="number" placeholder="0.00"/> <sl:input-group-addon><sl:input-group-text>€</sl:input-group-text></sl:input-group-addon> <sl:input-group-addon align="inline-end"><sl:input-group-text>EUR</sl:input-group-text></sl:input-group-addon> </sl:input-group> <sl:field-description>Excluding VAT.</sl:field-description> <sl:field-error/> </sl:field></form>Thymeleaf source
<sl:field th:field="*{budget}"> <sl:field-label>Budget</sl:field-label> <sl:input-group> <sl:input-group-input type="number" placeholder="0.00"/> <sl:input-group-addon><sl:input-group-text>€</sl:input-group-text></sl:input-group-addon> <sl:input-group-addon align="inline-end"><sl:input-group-text>EUR</sl:input-group-text></sl:input-group-addon> </sl:input-group> <sl:field-description>Excluding VAT.</sl:field-description> <sl:field-error/></sl:field>Rendered HTML
<div class="field"> <label class="label field-label" for="budget">Budget</label> <div class="input-group" role="group"> <input class="input-group-input" type="number" placeholder="0.00" id="budget" aria-describedby="budget-description" name="budget" value=""> <div class="input-group-addon" role="group"> <span class="input-group-text">€</span> </div> <div class="input-group-addon" role="group" data-align="inline-end"> <span class="input-group-text">EUR</span> </div> </div> <p class="field-description" id="budget-description">Excluding VAT.</p></div>Outside a field, write th:field on the sl:input-group-input itself.
Invalid
Section titled “Invalid”When the field has errors, the input marks itself aria-invalid="true", and the group draws the destructive border
and ring.
Thymeleaf source
<sl:field th:field="*{packageWeight}"> <sl:field-label>Weight</sl:field-label> <sl:input-group> <sl:input-group-input type="number"/> <sl:input-group-addon align="inline-end"><sl:input-group-text>kg</sl:input-group-text></sl:input-group-addon> </sl:input-group> <sl:field-error/></sl:field>Rendered HTML
<div class="field" data-invalid="true"> <label class="label field-label" for="packageWeight">Weight</label> <div class="input-group" role="group"> <input class="input-group-input" type="number" aria-invalid="true" id="packageWeight" aria-describedby="packageWeight-error" name="packageWeight" value="-2"> <div class="input-group-addon" role="group" data-align="inline-end"> <span class="input-group-text">kg</span> </div> </div> <div class="field-error" id="packageWeight-error">must be greater than 0</div></div>Disabled
Section titled “Disabled”Thymeleaf source
<sl:input-group> <sl:input-group-input name="discount" value="10" disabled aria-label="Discount"/> <sl:input-group-addon align="inline-end"><sl:input-group-text>%</sl:input-group-text></sl:input-group-addon></sl:input-group>Rendered HTML
<div class="input-group" role="group"> <input class="input-group-input" type="text" name="discount" value="10" disabled aria-label="Discount"> <div class="input-group-addon" role="group" data-align="inline-end"> <span class="input-group-text">%</span> </div></div>Read-only
Section titled “Read-only”As a read-only input: the group gets a muted background, and the value can still be focused, selected and copied.
Thymeleaf source
<sl:input-group> <sl:input-group-input name="volume" value="1.5" readonly aria-label="Volume"/> <sl:input-group-addon align="inline-end"><sl:input-group-text>l</sl:input-group-text></sl:input-group-addon></sl:input-group>Rendered HTML
<div class="input-group" role="group"> <input class="input-group-input" type="text" name="volume" value="1.5" readonly aria-label="Volume"> <div class="input-group-addon" role="group" data-align="inline-end"> <span class="input-group-text">l</span> </div></div>Attributes
Section titled “Attributes”sl:input-group
Section titled “sl:input-group”sl:input-group declares no props; every attribute passes through.
sl:input-group-addon
Section titled “sl:input-group-addon”| Attribute | Values | Default | Description |
|---|---|---|---|
align | inline-start inline-end block-start block-end | inline-start | Where the addon goes: before or after the control on its line, or above or below it across the whole group. |
border | boolean | — | With align block-start or block-end, a dividing line between the addon and the control, with matching spacing. |
Every prop can also be set from an expression: th:align="${...}". Any other attribute passes through to the rendered element, and class is appended.
sl:input-group-text
Section titled “sl:input-group-text”sl:input-group-text declares no props; every attribute passes through.
sl:input-group-button
Section titled “sl:input-group-button”| Attribute | Values | Default | Description |
|---|---|---|---|
variant | default outline ghost destructive secondary link | ghost | Visual style of the button, as on sl:button. |
size | xs sm icon-xs icon-sm | xs | Size of the button. The icon sizes are square, for a button that holds only an icon, and need an aria-label. |
Every prop can also be set from an expression: th:variant="${...}". Any other attribute passes through to the rendered element, and class is appended. An aria-label or aria-labelledby is required when size is icon-xs or icon-sm; rendering fails without one.
sl:input-group-input
Section titled “sl:input-group-input”| Attribute | Values | Default | Description |
|---|---|---|---|
type | text email password number search tel url date datetime-local month week time | text | Type of the input. |
Every prop can also be set from an expression: th:type="${...}". Any other attribute passes through to the rendered element, and class is appended.
sl:input-group-textarea
Section titled “sl:input-group-textarea”sl:input-group-textarea declares no props; every attribute passes through.