Select
Custom listbox on the Popover API — trigger button plus popover list with check mark, groups and disabled options. Native select stays for simple forms.
INSTALL
1pnpm dlx @sayagodev/mo add selectOr import the files from the package (no copy):
1import "@sayagodev/mo/css/select.css";2import "@sayagodev/mo/css/shared.css";3import "@sayagodev/mo/js/select.js";
EXAMPLES
Basic
1<mo-select data-placeholder="Select a fruit">2 <button type="button" popovertarget="ex-sel-fruit" data-select-trigger aria-label="Fruit"></button>3 <ul popover id="ex-sel-fruit" class="popover" role="listbox" aria-label="Fruits">4 <li><button type="button" role="option" value="apple">Apple</button></li>5 <li><button type="button" role="option" value="banana">Banana</button></li>6 <li><button type="button" role="option" value="blueberry">Blueberry</button></li>7 <li><button type="button" role="option" value="grapes">Grapes</button></li>8 <li><button type="button" role="option" value="pineapple">Pineapple</button></li>9 </ul>10</mo-select>11
Groups
-
Fruits
-
Vegetables
1<mo-select data-placeholder="Select food">2 <button type="button" popovertarget="ex-sel-food" data-select-trigger aria-label="Food"></button>3 <ul popover id="ex-sel-food" class="popover" role="listbox" aria-label="Food groups">4 <li role="group">5 <div data-label>Fruits</div>6 <button type="button" role="option" value="apple">Apple</button>7 <button type="button" role="option" value="banana">Banana</button>8 <button type="button" role="option" value="blueberry">Blueberry</button>9 </li>10 <hr />11 <li role="group">12 <div data-label>Vegetables</div>13 <button type="button" role="option" value="carrot">Carrot</button>14 <button type="button" role="option" value="potato">Potato</button>15 <button type="button" role="option" value="tomato">Tomato</button>16 </li>17 </ul>18</mo-select>19
Disabled
1<mo-select data-placeholder="Select a fruit">2 <button type="button" popovertarget="ex-sel-disabled" data-select-trigger aria-label="Fruit"></button>3 <ul popover id="ex-sel-disabled" class="popover" role="listbox" aria-label="Fruits">4 <li><button type="button" role="option" value="apple">Apple</button></li>5 <li><button type="button" role="option" value="banana" disabled>Banana</button></li>6 <li><button type="button" role="option" value="blueberry">Blueberry</button></li>7 <li><button type="button" role="option" value="grapes">Grapes</button></li>8 <li><button type="button" role="option" value="pineapple">Pineapple</button></li>9 </ul>10</mo-select>11
Fully disabled
1<mo-select data-placeholder="Disabled">2 <button type="button" popovertarget="ex-sel-fully-disabled" data-select-trigger aria-label="Fruit" disabled></button>3 <ul popover id="ex-sel-fully-disabled" class="popover" role="listbox" aria-label="Fruits">4 <li><button type="button" role="option" value="apple">Apple</button></li>5 <li><button type="button" role="option" value="banana">Banana</button></li>6 <li><button type="button" role="option" value="blueberry">Blueberry</button></li>7 </ul>8</mo-select>9
Invalid
1<mo-select data-placeholder="Error state">2 <button type="button" popovertarget="ex-sel-invalid" data-select-trigger aria-label="Fruit" aria-invalid="true"></button>3 <ul popover id="ex-sel-invalid" class="popover" role="listbox" aria-label="Fruits">4 <li><button type="button" role="option" value="apple">Apple</button></li>5 <li><button type="button" role="option" value="banana">Banana</button></li>6 <li><button type="button" role="option" value="blueberry">Blueberry</button></li>7 </ul>8</mo-select>9
Scrollable
1<mo-select data-placeholder="Select timezone">2 <button type="button" popovertarget="ex-sel-tz" data-select-trigger aria-label="Timezone"></button>3 <ul popover id="ex-sel-tz" class="popover" role="listbox" aria-label="Timezones">4 <li><button type="button" role="option" value="gmt-11">GMT-11:00 Midway Island</button></li>5 <li><button type="button" role="option" value="gmt-10">GMT-10:00 Hawaii</button></li>6 <li><button type="button" role="option" value="gmt-8">GMT-08:00 Pacific Time</button></li>7 <li><button type="button" role="option" value="gmt-7">GMT-07:00 Mountain Time</button></li>8 <li><button type="button" role="option" value="gmt-6">GMT-06:00 Central Time</button></li>9 <li><button type="button" role="option" value="gmt-5">GMT-05:00 Eastern Time</button></li>10 <li><button type="button" role="option" value="gmt-3">GMT-03:00 Brasilia Time</button></li>11 <li><button type="button" role="option" value="gmt+0">GMT+00:00 UTC</button></li>12 <li><button type="button" role="option" value="gmt+1">GMT+01:00 Central European Time</button></li>13 <li><button type="button" role="option" value="gmt+2">GMT+02:00 Eastern European Time</button></li>14 <li><button type="button" role="option" value="gmt+3">GMT+03:00 Moscow Time</button></li>15 <li><button type="button" role="option" value="gmt+4">GMT+04:00 Gulf Standard Time</button></li>16 <li><button type="button" role="option" value="gmt+5:30">GMT+05:30 India Time</button></li>17 <li><button type="button" role="option" value="gmt+7">GMT+07:00 Indochina Time</button></li>18 <li><button type="button" role="option" value="gmt+8">GMT+08:00 China Standard Time</button></li>19 <li><button type="button" role="option" value="gmt+9">GMT+09:00 Japan Standard Time</button></li>20 <li><button type="button" role="option" value="gmt+10">GMT+10:00 Eastern Australia Time</button></li>21 <li><button type="button" role="option" value="gmt+12">GMT+12:00 New Zealand Time</button></li>22 </ul>23</mo-select>24
USAGE
1<mo-select data-placeholder="Select a fruit">2 <button type="button" popovertarget="sel1" data-select-trigger aria-label="Fruit"></button>3 <ul popover id="sel1" class="popover" role="listbox" aria-label="Fruits">4 <li><button type="button" role="option" value="apple">Apple</button></li>5 <li><button type="button" role="option" disabled>Banana</button></li>6 <li role="group"><div data-label>Citrus</div>7 <button type="button" role="option" value="lemon">Lemon</button>8 </li>9 </ul>10</mo-select>
API
NAMEKINDDESCRIPTION
<mo-select>elementOrchestrator: roving [aria-selected], typeahead, placement with flip; emits mo-select-change
data-placeholderattributeTrigger label while no value is chosen (dimmed via [data-empty])
[data-select-trigger]attributeTrigger button; chevron drawn via ::after
[role=option] + valueattributeOption row; text is the label, value feeds events (falls back to the text)
disabled / aria-disabledattributeSkipped by arrows and typeahead, unclickable
[data-label]attributeGroup heading inside li[role=group]
mo-select-changeelementEvent, detail { value } on pick
--mo-select-*tokenheight, padding-x, radius, min-width, max-height hooks
FILES
src/css/select.csssrc/css/shared.csssrc/js/select.jsInstall:
mo add selectTHEMING · 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
--accent--accent-foreground--border--destructive--foreground--input--muted-foreground--radius--ringComponent hooks
--mo-select-height--mo-select-max-height--mo-select-min-width--mo-select-padding-x--mo-select-radiusPreview
Your CSS
Try it: type --mo- for the component hooks, or edit any value — the preview obeys in real time.