Github

Input group

.input-group owns the border and focus ring. .input-addon is prefix or suffix. data-align="end" for inline-end, block-start / block-end for a header or footer (textarea). Wrap with .button-group and .button-group-text for a prefix/suffix around the group. There is no wrapper. Paste the markup first. Tooltip addons use the Tooltip page’s manual popover. A dropdown addon is a ghost button in .input-addon plus a .menu popover (same as Dropdown). Copy src/components/ui/Place.svelte (see Popover). Spinner addons can import src/components/ui/Spinner.svelte (Spinner).

Icon

<div class="input-group">
	<span class="input-addon" aria-hidden="true">
		<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
			<circle cx="11" cy="11" r="7"></circle>
			<path d="m20 20-3-3"></path>
		</svg>
	</span>
	<input class="input" type="search" placeholder="Search...">
</div>

Text

$ USD
https:// .com
@acme.test
<div class="input-group">
	<span class="input-addon">lt;/span>
	<input class="input" type="text" placeholder="0.00">
	<span class="input-addon" data-align="end">USD</span>
</div>

<div class="input-group">
	<span class="input-addon">https://</span>
	<input class="input" type="text" placeholder="example.com">
	<span class="input-addon" data-align="end">.com</span>
</div>

<div class="input-group">
	<input class="input" type="text" placeholder="username">
	<span class="input-addon" data-align="end">@acme.test</span>
</div>

Button

<div class="input-group">
	<input class="input" type="url" value="https://alumna.dev" readonly>
	<span class="input-addon" data-align="end">
		<button class="button button-ghost button-sm" type="button">Copy</button>
	</span>
</div>

<div class="input-group">
	<input class="input" type="search" placeholder="Type to search...">
	<button class="button button-secondary button-sm" type="button">Search</button>
</div>

Textarea

data-align="block-start" is a header, block-end a footer. Both turn the group into a column.

52% used
<div class="input-group">
	<textarea class="textarea" placeholder="Ask, search or chat…"></textarea>
	<span class="input-addon" data-align="block-end">
		<span>52% used</span>
		<button class="button button-sm" type="button">Send</button>
	</span>
</div>

<div class="input-group">
	<textarea class="textarea" id="prompt"></textarea>
	<span class="input-addon" data-align="block-start">
		<label class="label" for="prompt">Ask, search or chat…</label>
	</span>
</div>

<div class="input-group">
	<textarea class="textarea" placeholder="Share your thoughts…"></textarea>
	<span class="input-addon" data-align="block-end">
		<button class="button button-ghost button-sm" type="button">Cancel</button>
		<button class="button button-sm" type="button">Post</button>
	</span>
</div>

Spinner

Saving…
<div class="input-group">
	<input class="input" placeholder="Searching…" disabled>
	<span class="input-addon" data-align="end">
		<svg class="spinner" viewBox="0 0 24 24" fill="none" aria-hidden="true">
			<circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2" opacity="0.25"></circle>
			<path d="M21 12a9 9 0 0 1-9 9" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
		</svg>
	</span>
</div>

Same addon with src/components/ui/Spinner.svelte (Spinner):

<script>
	import Spinner from './ui/Spinner.svelte';
</script>

<div class="input-group">
	<input class="input" placeholder="Searching…" disabled>
	<span class="input-addon" data-align="end">
		<Spinner decorative />
	</span>
</div>

Label

<div class="input-group">
	<span class="input-addon"><label class="label" for="handle">@</label></span>
	<input class="input" id="handle" placeholder="alumna">
</div>

Tooltip

Must start with https://
<script>
	import { tip_enter, tip_leave } from './ui/Place.svelte';
	let tip;
</script>

<div class="input-group">
	<input class="input" placeholder="Enter a URL">
	<span class="input-addon" data-align="end">
		<button class="button button-ghost button-sm button-icon" type="button" aria-label="About this field"
			onmouseenter={(event) => tip_enter(event, tip)}
			onmouseleave={(event) => tip_leave(event, tip)}
		>?</button>
	</span>
</div>
<div bind:this={tip} popover="manual" class="tooltip">Must start with https://</div>
<script>
	import { on_menu_toggle, menu_keydown, choose_item } from './ui/Place.svelte';
	let scheme = $state('https://');
</script>

<div class="input-group">
	<span class="input-addon">
		<button class="button button-ghost button-sm" type="button" popovertarget="scheme" aria-haspopup="menu" aria-expanded="false">{scheme}</button>
	</span>
	<input class="input" placeholder="example.com">
</div>
<div id="scheme" popover class="menu" role="menu" tabindex="-1" ontoggle={on_menu_toggle} onkeydown={menu_keydown}>
	<button class="menu-item" type="button" role="menuitem" onclick={(event) => { scheme = 'https://'; choose_item(event); }}>https://</button>
	<button class="menu-item" type="button" role="menuitem" onclick={(event) => { scheme = 'http://'; choose_item(event); }}>http://</button>
</div>

Kbd

K
<div class="input-group">
	<span class="input-addon" aria-hidden="true">
		<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
			<circle cx="11" cy="11" r="7"></circle>
			<path d="m20 20-3-3"></path>
		</svg>
	</span>
	<input class="input" type="search" placeholder="Search…">
	<span class="input-addon" data-align="end">
		<kbd class="kbd">⌘</kbd>
		<kbd class="kbd">K</kbd>
	</span>
</div>

Button group

Prefix and suffix sit in .button-group-text around the input group. Shared radii come from .button-group.

.com
<div class="button-group" style="width: 100%">
	<span class="button-group-text"><label class="label" for="url">https://</label></span>
	<div class="input-group">
		<input class="input" id="url" placeholder="example">
		<span class="input-addon" data-align="end" aria-hidden="true">
			<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
				<path d="M10 13a5 5 0 0 0 7.07 0l1.41-1.41a5 5 0 0 0-7.07-7.07L10 5.93"></path>
				<path d="M14 11a5 5 0 0 0-7.07 0L5.52 12.41a5 5 0 0 0 7.07 7.07L14 18.07"></path>
			</svg>
		</span>
	</div>
	<span class="button-group-text">.com</span>
</div>

In card

Contact
Input groups inside a card.
https://
<div class="card" style="max-width: 24rem">
	<div class="card-header">
		<div class="card-title">Contact</div>
		<div class="card-description">Input groups inside a card.</div>
	</div>
	<div class="card-content">
		<div class="field-group">
			<div class="field">
				<label class="label" for="email">Email</label>
				<div class="input-group">
					<input class="input" id="email" type="email" placeholder="you@example.com">
					<span class="input-addon" data-align="end" aria-hidden="true">
						<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
							<rect x="3" y="5" width="18" height="14" rx="2"></rect>
							<path d="m3 7 9 6 9-6"></path>
						</svg>
					</span>
				</div>
			</div>
			<div class="field">
				<label class="label" for="site">Website</label>
				<div class="input-group">
					<span class="input-addon">https://</span>
					<input class="input" id="site" placeholder="example.com">
				</div>
			</div>
		</div>
	</div>
	<div class="card-footer" style="justify-content: flex-end; gap: 0.5rem">
		<button class="button button-outline" type="button">Cancel</button>
		<button class="button" type="button">Submit</button>
	</div>
</div>

Invalid and disabled

aria-invalid="true" on a control tints the group. data-disabled on the group fades it (keep the control disabled too). A spinner next to a disabled input does not need the fade.

.com
$
<div class="input-group">
	<input class="input" type="email" placeholder="hello@" aria-invalid="true">
	<span class="input-addon" data-align="end">.com</span>
</div>

<div class="input-group" data-disabled>
	<span class="input-addon">lt;/span>
	<input class="input" placeholder="0.00" disabled>
</div>