Field composition
Form row primitive: [data-field] owns spacing, fieldset/legend groups sections, smalls carry hints and errors.
INSTALL
1pnpm dlx @sayagodev/mo add formOr import the files from the package (no copy):
1import "@sayagodev/mo/css/form.css";EXAMPLES
Checkout form
1<form>2 <fieldset>3 <legend>Payment Method</legend>4 <p class="text-light">All transactions are secure and encrypted.</p>5 <div data-field>6 <label for="ex-card-name">Name on Card</label>7 <input id="ex-card-name" required placeholder="Evil Rabbit" />8 </div>9 <div data-field>10 <label for="ex-card-number">Card Number</label>11 <input id="ex-card-number" required placeholder="1234 5678 9012 3456" />12 <small data-hint>Enter your 16-digit card number.</small>13 </div>14 <div class="row">15 <div class="col-4">16 <div data-field>17 <label for="ex-exp-month">Month</label>18 <mo-select data-placeholder="MM">19 <button type="button" popovertarget="ex-exp-month" data-select-trigger aria-label="Month"></button>20 <ul popover id="ex-exp-month" class="popover" role="listbox">21 <li><button type="button" role="option" value="01">01</button></li>22 <li><button type="button" role="option" value="02">02</button></li>23 <li><button type="button" role="option" value="03">03</button></li>24 <li><button type="button" role="option" value="04">04</button></li>25 <li><button type="button" role="option" value="05">05</button></li>26 <li><button type="button" role="option" value="06">06</button></li>27 <li><button type="button" role="option" value="07">07</button></li>28 <li><button type="button" role="option" value="08">08</button></li>29 <li><button type="button" role="option" value="09">09</button></li>30 <li><button type="button" role="option" value="10">10</button></li>31 <li><button type="button" role="option" value="11">11</button></li>32 <li><button type="button" role="option" value="12">12</button></li>33 </ul>34 </mo-select>35 </div>36 </div>37 <div class="col-4">38 <div data-field>39 <label for="ex-exp-year">Year</label>40 <mo-select data-placeholder="YYYY">41 <button type="button" popovertarget="ex-exp-year" data-select-trigger aria-label="Year"></button>42 <ul popover id="ex-exp-year" class="popover" role="listbox">43 <li><button type="button" role="option" value="2025">2025</button></li>44 <li><button type="button" role="option" value="2026">2026</button></li>45 <li><button type="button" role="option" value="2027">2027</button></li>46 <li><button type="button" role="option" value="2028">2028</button></li>47 <li><button type="button" role="option" value="2029">2029</button></li>48 </ul>49 </mo-select>50 </div>51 </div>52 <div class="col-4">53 <div data-field>54 <label for="ex-cvv">CVV</label>55 <input id="ex-cvv" required placeholder="123" />56 </div>57 </div>58 </div>59 </fieldset>60 <fieldset>61 <legend>Billing Address</legend>62 <p class="text-light">The billing address associated with your payment method.</p>63 <label class="flex items-center gap-2 mt-2">64 <input type="checkbox" checked /> Same as shipping address65 </label>66 </fieldset>67 <fieldset>68 <div data-field>69 <label for="ex-comments">Comments</label>70 <textarea id="ex-comments" placeholder="Add any additional comments"></textarea>71 </div>72 </fieldset>73 <div class="hstack">74 <button type="submit">Submit</button>75 <button type="button" class="outline">Cancel</button>76 </div>77</form>78
Notification groups
1<div class="vstack gap-6">2 <fieldset>3 <legend>Responses</legend>4 <p class="text-light">Get notified when ChatGPT responds to requests that take time, like research or image generation.</p>5 <label class="flex items-center gap-2 mt-2">6 <input type="checkbox" checked disabled /> Push notifications7 </label>8 </fieldset>9 <fieldset>10 <legend>Tasks</legend>11 <p class="text-light">Get notified when tasks you've created have updates. <a href="#">Manage tasks</a></p>12 <label class="flex items-center gap-2 mt-2">13 <input type="checkbox" id="ex-tasks-push" /> Push notifications14 </label>15 <label class="flex items-center gap-2 mt-2">16 <input type="checkbox" id="ex-tasks-email" /> Email notifications17 </label>18 </fieldset>19</div>20
Username & password
1<fieldset>2 <div data-field>3 <label for="ex-fs-username">Username</label>4 <input id="ex-fs-username" placeholder="Max Leiter" />5 <small data-hint>Choose a unique username for your account.</small>6 </div>7 <div data-field>8 <label for="ex-fs-password">Password</label>9 <small data-hint>Must be at least 8 characters long.</small>10 <input id="ex-fs-password" type="password" placeholder="••••••••" />11 </div>12</fieldset>13
Checkbox list
1<fieldset>2 <legend>Show these items on the desktop</legend>3 <p class="text-light">Select the items you want to show on the desktop.</p>4 <label class="flex items-center gap-2 mt-2">5 <input type="checkbox" checked /> Hard disks6 </label>7 <label class="flex items-center gap-2 mt-2">8 <input type="checkbox" /> External disks9 </label>10 <label class="flex items-center gap-2 mt-2">11 <input type="checkbox" /> CDs, DVDs, and iPods12 </label>13 <label class="flex items-center gap-2 mt-2">14 <input type="checkbox" /> Connected servers15 </label>16</fieldset>17<hr />18<label class="flex items-center justify-between gap-4">19 <span class="vstack gap-1">20 Sync Desktop & Documents folders21 <span class="text-light">Your Desktop & Documents folders are being synced with iCloud Drive. You can access them from other devices.</span>22 </span>23 <input type="checkbox" checked />24</label>25
Radio plan picker
1<fieldset>2 <legend>Subscription Plan</legend>3 <p class="text-light">Yearly and lifetime plans offer significant savings.</p>4 <label class="flex items-center gap-2 mt-2">5 <input type="radio" name="ex-plan" checked /> Monthly ($9.99/month)6 </label>7 <label class="flex items-center gap-2 mt-2">8 <input type="radio" name="ex-plan" /> Yearly ($99.99/year)9 </label>10 <label class="flex items-center gap-2 mt-2">11 <input type="radio" name="ex-plan" /> Lifetime ($299.99)12 </label>13</fieldset>14
Department select
Select your department or area of work.
1<div data-field>2 <label for="ex-dept">Department</label>3 <mo-select data-placeholder="Choose department">4 <button type="button" popovertarget="ex-dept" data-select-trigger aria-label="Department"></button>5 <ul popover id="ex-dept" class="popover" role="listbox">6 <li><button type="button" role="option" value="engineering">Engineering</button></li>7 <li><button type="button" role="option" value="design">Design</button></li>8 <li><button type="button" role="option" value="marketing">Marketing</button></li>9 <li><button type="button" role="option" value="sales">Sales</button></li>10 <li><button type="button" role="option" value="support">Customer Support</button></li>11 <li><button type="button" role="option" value="hr">Human Resources</button></li>12 <li><button type="button" role="option" value="finance">Finance</button></li>13 <li><button type="button" role="option" value="operations">Operations</button></li>14 </ul>15 </mo-select>16 <small data-hint>Select your department or area of work.</small>17</div>18
Switch row
1<label class="flex items-center gap-2">2 Multi-factor authentication3 <input type="checkbox" role="switch" id="ex-2fa" />4</label>5
Price range
Price Range
Set your budget range ($200 – $800).
Mò renders one native slider; dual-thumb ranges are not supported.1<div class="vstack gap-4 w-100">2 <strong>Price Range</strong>3 <p class="text-light">Set your budget range ($200 – $800).</p>4 <input type="range" min="0" max="1000" step="10" value="200" aria-label="Price Range" />5 <small data-hint>Mò renders one native slider; dual-thumb ranges are not supported.</small>6</div>7
Feedback textarea
1<fieldset>2 <div data-field>3 <label for="ex-feedback">Feedback</label>4 <textarea id="ex-feedback" rows="4" placeholder="Your feedback helps us improve..."></textarea>5 <small data-hint>Share your thoughts about our service.</small>6 </div>7</fieldset>8
Address fieldset
1<fieldset>2 <legend>Address Information</legend>3 <p class="text-light">We need your address to deliver your order.</p>4 <div data-field>5 <label for="ex-street">Street Address</label>6 <input id="ex-street" placeholder="123 Main St" />7 </div>8 <div class="row">9 <div class="col-6">10 <div data-field>11 <label for="ex-city">City</label>12 <input id="ex-city" placeholder="New York" />13 </div>14 </div>15 <div class="col-6">16 <div data-field>17 <label for="ex-zip">Postal Code</label>18 <input id="ex-zip" placeholder="90502" />19 </div>20 </div>21 </div>22</fieldset>23
Profile form
1<form>2 <fieldset>3 <legend>Profile</legend>4 <p class="text-light">Fill in your profile information.</p>5 <div data-field>6 <label for="ex-profile-name">Name</label>7 <small data-hint>Provide your full name for identification.</small>8 <input id="ex-profile-name" required placeholder="Evil Rabbit" />9 </div>10 <div class="hstack">11 <button type="submit">Submit</button>12 <button type="button" class="outline">Cancel</button>13 </div>14 </fieldset>15</form>16
Choice cards
Compute Environment
Select the compute environment for your cluster.
1<div class="vstack gap-4">2 <span class="vstack gap-1">3 <strong>Compute Environment</strong>4 <span class="text-light">Select the compute environment for your cluster.</span>5 </span>6 <label class="flex items-center justify-between gap-4">7 <span class="vstack gap-1">8 <strong>Kubernetes</strong>9 <span class="text-light">Run GPU workloads on a K8s cluster.</span>10 </span>11 <input type="radio" name="ex-compute" value="kubernetes" checked />12 </label>13 <label class="flex items-center justify-between gap-4">14 <span class="vstack gap-1">15 <strong>Virtual Machine</strong>16 <span class="text-light">Access a cluster to run GPU workloads.</span>17 </span>18 <input type="radio" name="ex-compute" value="vm" />19 </label>20</div>21
USAGE
1<fieldset>2 <legend>Section</legend>3 <div data-field>4 <label for="id">Label</label>5 <input id="id" />6 <small data-hint>Hint</small>7 </div>8</fieldset>
API
NAMEKINDDESCRIPTION
[data-field]attributeColumn flex wrapper, owns all spacing
fieldset > legendelementSection title in a bordered group
small[data-hint]elementMuted description/hint line
.row / .col-*classResponsive multi-column field grids
.hstack / .vstackclassHorizontal rows, stacked sections
FILES
src/css/form.cssInstall:
mo add formTHEMING · 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
--background--border--destructive--foreground--input--muted--muted-foreground--primary--primary-foreground--radius--ringComponent hooks
--mo-field-gap--mo-input-padding-x--mo-input-radius--mo-slider-thumb-bg--mo-switch-thumb--mo-switch-widthPreview
Your CSS
Try it: type --mo- for the component hooks, or edit any value — the preview obeys in real time.