Input Group
Input, textarea or select joined with addons — icons, text, kbd hints — inside one bordered frame that owns focus and error states.
INSTALL
1pnpm dlx @sayagodev/mo add input-groupOr import the files from the package (no copy):
1import "@sayagodev/mo/css/input-group.css";EXAMPLES
Label & hint
This is a hint for the input group.
This field contains validation errors.
1<div class="vstack gap-4">2 <div data-field>3 <label for="ig-basic-default">Default (no input group)</label>4 <input id="ig-basic-default" placeholder="Placeholder" />5 </div>6 <div data-field>7 <label for="ig-basic-group">Input group</label>8 <div data-input-group>9 <input id="ig-basic-group" placeholder="Placeholder" />10 </div>11 <span data-hint>This is a hint for the input group.</span>12 </div>13 <div data-field>14 <label for="ig-basic-disabled">Disabled</label>15 <div data-input-group>16 <input id="ig-basic-disabled" placeholder="This field is disabled" disabled />17 </div>18 </div>19 <div data-field aria-invalid="true">20 <label for="ig-basic-invalid">Invalid</label>21 <div data-input-group>22 <input id="ig-basic-invalid" placeholder="This field is invalid" aria-invalid="true" />23 </div>24 <small class="error">This field contains validation errors.</small>25 </div>26</div>27
Icon addons
1<div class="vstack gap-4">2 <div data-input-group>3 <span data-addon>4 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>5 </span>6 <input placeholder="Search..." aria-label="Search" />7 </div>8 <div data-input-group>9 <span data-addon>10 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="20" height="16" x="2" y="4" rx="2" /><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /></svg>11 </span>12 <input type="email" placeholder="Enter your email" aria-label="Email" />13 </div>14 <div data-input-group>15 <span data-addon>16 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>17 </span>18 <input placeholder="Card number" aria-label="Card number" />19 <span data-addon>20 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>21 </span>22 </div>23 <div data-input-group>24 <input placeholder="Card number" aria-label="Card number" />25 <span data-addon>26 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" /></svg>27 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg>28 </span>29 </div>30</div>31
Text & select joins
https://
.dev
Total
USD
Dims with its control
1<div class="vstack gap-4">2 <div data-input-group>3 <span data-addon>https://</span>4 <input placeholder="example.com/page" aria-label="URL path" />5 <span data-addon>.dev</span>6 </div>7 <div data-input-group>8 <span data-addon>Total</span>9 <input value="1,250.00" inputmode="decimal" aria-label="Total" />10 <span data-addon>USD</span>11 </div>12 <div data-input-group>13 <mo-select data-placeholder="https://">14 <button type="button" popovertarget="ig-proto" data-select-trigger aria-label="Protocol"></button>15 <ul popover id="ig-proto" class="popover" role="listbox">16 <li><button type="button" role="option" value="https://" aria-selected="true">https://</button></li>17 <li><button type="button" role="option" value="http://">http://</button></li>18 </ul>19 </mo-select>20 <input placeholder="example.com" aria-label="Domain" />21 <mo-select data-placeholder=".com">22 <button type="button" popovertarget="ig-tld" data-select-trigger aria-label="TLD"></button>23 <ul popover id="ig-tld" class="popover" role="listbox">24 <li><button type="button" role="option" value=".com" aria-selected="true">.com</button></li>25 <li><button type="button" role="option" value=".dev">.dev</button></li>26 <li><button type="button" role="option" value=".org">.org</button></li>27 </ul>28 </mo-select>29 </div>30 <div data-input-group>31 <input id="ig-addons-disabled" placeholder="This field is disabled" disabled aria-label="Disabled example" />32 <span data-addon>Dims with its control</span>33 </div>34</div>35
Kbd shortcuts
⌘K
⌘P
Press the shortcut from anywhere to open this filter.
1<div class="vstack gap-4">2 <div data-input-group>3 <input placeholder="Press ⌘K to focus…" aria-label="Command palette trigger" />4 <span data-addon><kbd>⌘K</kbd></span>5 </div>6 <div data-field>7 <label for="ig-kbd-hint">Shortcut hint in a field</label>8 <div data-input-group>9 <input id="ig-kbd-hint" placeholder="Filter issues…" />10 <span data-addon><kbd>⌘P</kbd></span>11 </div>12 <span data-hint>Press the shortcut from anywhere to open this filter.</span>13 </div>14</div>15
Buttons in addons
Your connection is not secure.
@
Enter a valid handle.
1<div class="vstack gap-4">2 <div data-input-group>3 <input placeholder="https://x.com/shadcn" value="https://x.com/shadcn" readonly aria-label="Profile URL" />4 <span data-addon>5 <button type="button" class="icon ghost" aria-label="Copy">6 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>7 </button>8 </span>9 </div>10 <div data-input-group>11 <span data-addon>12 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg>13 Your connection is not secure.14 </span>15 <input aria-label="Address" />16 </div>17 <div data-input-group>18 <input placeholder="Type to search..." aria-label="Search" />19 <span data-addon>20 <button type="button" class="ghost">Search</button>21 </span>22 </div>23 <div data-field aria-invalid="true">24 <label for="ig-button-invalid">Invalid turns addon hairlines destructive</label>25 <div data-input-group>26 <span data-addon>@</span>27 <input id="ig-button-invalid" value="not-an-email" aria-invalid="true" />28 <span data-addon>29 <button type="button" class="icon ghost" aria-label="Clear">30 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>31 </button>32 </span>33 </div>34 <small class="error">Enter a valid handle.</small>35 </div>36</div>37
Invalid hairlines
Enter a valid email address.
@
.dev
1<div class="vstack gap-4">2 <div data-field aria-invalid="true">3 <label for="ig-invalid-addon">Invalid with addons</label>4 <div data-input-group>5 <span data-addon>6 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="20" height="16" x="2" y="4" rx="2" /><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /></svg>7 </span>8 <input id="ig-invalid-addon" type="email" value="not-an-email" aria-invalid="true" />9 <span data-addon>10 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg>11 </span>12 </div>13 <small class="error">Enter a valid email address.</small>14 </div>15 <div data-input-group aria-invalid="true">16 <span data-addon>@</span>17 <input placeholder="handle" aria-label="Handle" />18 <span data-addon>.dev</span>19 </div>20</div>21
Textarea header/footer
script.js
Line 1, Column 1
JavaScript
1<div class="vstack gap-4">2 <div data-input-group>3 <span data-addon data-align="block-start">4 <span class="flex items-center gap-2">5 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="16 18 22 12 16 6" /><polyline points="8 6 2 12 8 18" /></svg>6 script.js7 </span>8 <button type="button" class="icon ghost" data-push aria-label="Reset">9 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" /><path d="M21 3v5h-5" /><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" /><path d="M8 16H3v5" /></svg>10 </button>11 <button type="button" class="icon ghost" aria-label="Copy">12 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>13 </button>14 </span>15 <textarea placeholder="console.log('Hello, world!');" aria-label="Code editor"></textarea>16 <span data-addon data-align="block-end">17 Line 1, Column 118 <span data-push>JavaScript</span>19 </span>20 </div>21 <div data-input-group>22 <textarea placeholder="Share your thoughts..." aria-label="Comment"></textarea>23 <span data-addon data-align="block-end">24 <span data-push></span>25 <button type="button" class="ghost">Cancel</button>26 <button type="button">Post comment</button>27 </span>28 </div>29 <div data-field>30 <label for="ig-textarea-plain">Default textarea (no input group)</label>31 <textarea id="ig-textarea-plain" placeholder="Enter your text here..."></textarea>32 </div>33</div>34
Spinner
⌘K
Validating…
1<div class="vstack gap-4">2 <div data-input-group>3 <span data-addon>4 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>5 </span>6 <input placeholder="Search..." aria-label="Search" />7 <span data-addon>8 <kbd>⌘K</kbd>9 </span>10 </div>11 <div data-input-group>12 <input placeholder="Send a message..." disabled aria-label="Message" />13 <span data-addon aria-hidden="true">14 <span aria-busy="true" data-spinner="small"></span>15 </span>16 </div>17 <div data-input-group>18 <textarea placeholder="Send a message..." disabled aria-label="Message"></textarea>19 <span data-addon data-align="block-end">20 <span class="flex items-center gap-2">21 <span aria-busy="true" data-spinner="small"></span>22 Validating…23 </span>24 <button type="button" class="icon" data-push aria-label="Send">25 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 12 7-7 7 7" /><path d="M12 19V5" /></svg>26 </button>27 </span>28 </div>29</div>30
Dropdown addon
1<div class="vstack gap-4" style="max-width: 22rem;">2 <div data-input-group>3 <input placeholder="Enter file name" aria-label="File name" />4 <span data-addon>5 <mo-dropdown>6 <button type="button" class="icon ghost icon-xs" aria-label="More" popovertarget="ig-dd-file">7 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="5" r="1" /><circle cx="12" cy="12" r="1" /><circle cx="12" cy="19" r="1" /></svg>8 </button>9 <menu popover id="ig-dd-file">10 <button role="menuitem" type="button">Settings</button>11 <button role="menuitem" type="button">Copy path</button>12 <button role="menuitem" type="button">Open location</button>13 </menu>14 </mo-dropdown>15 </span>16 </div>17 <div data-input-group>18 <input placeholder="Enter search query" aria-label="Search query" />19 <span data-addon>20 <mo-dropdown>21 <button type="button" class="ghost xs" popovertarget="ig-dd-search">22 Search in...23 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>24 </button>25 <menu popover id="ig-dd-search">26 <button role="menuitem" type="button">Documentation</button>27 <button role="menuitem" type="button">Blog Posts</button>28 <button role="menuitem" type="button">Changelog</button>29 </menu>30 </mo-dropdown>31 </span>32 </div>33</div>34
Prompt & counter
Ask, search or chat...
54 / 280 characters
Plain text
1<div class="vstack gap-4" style="max-width: 24rem;">2 <div data-input-group>3 <span data-addon data-align="block-start">4 Ask, search or chat...5 <svg data-push xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg>6 </span>7 <textarea rows="3" aria-label="Prompt"></textarea>8 <span data-addon data-align="block-end">9 <button type="button">Ask</button>10 <button type="button" class="icon ghost icon-xs" data-push aria-label="Send">11 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 12 14 0" /><path d="m12 5 7 7-7 7" /></svg>12 </button>13 </span>14 </div>15 <div data-input-group>16 <textarea rows="3" aria-label="Message with counter">Mò UI renders shadcn anatomy with plain semantic tags.</textarea>17 <span data-addon data-align="block-end">18 54 / 280 characters19 <span data-push>Plain text</span>20 </span>21 </div>22</div>23
USAGE
1<div data-input-group>2 <span data-addon>https://</span>3 <input placeholder="example.com" />4 <span data-addon><kbd>⌘K</kbd></span>5</div>
API
NAMEKINDDESCRIPTION
[data-input-group]attributeSelects the component; aria-invalid lives here
[data-addon]attributeMuted addon strip beside/below the control
data-align="block-start/end"attributeFull-width addon rows above/below a textarea
[data-push]attributePushes trailing addon content to the far edge
--mo-input-group-*tokenHeight, radius, divider hooks
FILES
src/css/input-group.cssInstall:
mo add input-groupTHEMING · PLAYGROUND
This is the exact component from the examples above, wearing a completely different theme. Edit the tokens and --mo-* hooks in the editor — autocomplete knows them all — and the preview obeys in real time. Your CSS always wins.
Global tokens
--input--muted-foreground--radius--ringComponent hooks
--mo-input-group-addon-padding-x--mo-input-group-divider--mo-input-group-height--mo-input-group-radiusPreview
Your CSS
Try it: type --mo- for the component hooks, or edit any value — the preview obeys in real time.