Polished stylesheet

An example drop-in stylesheet — pure tag + data-* selectors, no class names in the markup. The library itself ships no CSS; this is just one opinionated look you can copy and tweak.

Used for sign-in and account recovery.
3-24 letters, numbers, or underscores.
Optional — link to your site or profile.
You can upgrade or downgrade any time.
Billing cycle
Only relevant for team plans.
Topics you care aboutPick at least one.
Profile
Up to 280 characters.
examples/styles/polished.css
/* ---------------------------------------------------------------
   json-schema-to-form — default stylesheet
   Pure tag + data-* selectors; zero class names in the markup.
   Drop-in on any form rendered by the library.
   --------------------------------------------------------------- */

:root {
  --jsf-bg:         #ffffff;
  --jsf-surface:    #ffffff;
  --jsf-surface-2:  #f7f8fa;
  --jsf-border:     #d9dde3;
  --jsf-border-2:   #c0c6cf;
  --jsf-text:       #1c1f24;
  --jsf-muted:      #5a6270;
  --jsf-accent:     #2563eb;
  --jsf-accent-bg:  #eff5ff;
  --jsf-danger:     #dc2626;
  --jsf-radius:     8px;
  --jsf-radius-sm:  6px;
  --jsf-gap:        10px;
  --jsf-pad-y:      9px;
  --jsf-pad-x:      12px;
  --jsf-font:       system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --jsf-shadow:     0 1px 2px rgba(15, 23, 42, 0.04);
  --jsf-focus:      0 0 0 3px rgba(37, 99, 235, 0.25);
}

form {
  font-family: var(--jsf-font);
  color: var(--jsf-text);
  display: flex;
  flex-direction: column;
  gap: 16px;
  line-height: 1.45;
}

/* --- every row --- */
form [data-name] {
  display: flex;
  flex-direction: column;
  gap: var(--jsf-gap);
}

/* --- labels & legends --- */
form [data-name] > label,
form [data-name] > legend {
  font-weight: 600;
  font-size: 0.9375rem;
  color: var(--jsf-text);
  padding: 0;
}

/* required marker — only the field's own label/legend, not option labels */
form [data-required]:not([data-variant="group"]) > label::after,
form [data-required] > legend::after {
  content: " *";
  color: var(--jsf-danger);
  font-weight: 700;
}

/* --- hint text --- */
form [data-name] > small {
  color: var(--jsf-muted);
  font-size: 0.8125rem;
  line-height: 1.4;
}

/* --- controls (text-like) --- */
form input,
form select,
form textarea {
  font: inherit;
  color: inherit;
  background: var(--jsf-surface);
  border: 1px solid var(--jsf-border);
  border-radius: var(--jsf-radius-sm);
  padding: var(--jsf-pad-y) var(--jsf-pad-x);
  width: 100%;
  box-sizing: border-box;
  box-shadow: var(--jsf-shadow);
  transition: border-color 120ms ease, box-shadow 120ms ease, background 120ms ease;
}

form input:hover,
form select:hover,
form textarea:hover {
  border-color: var(--jsf-border-2);
}

form input:focus,
form select:focus,
form textarea:focus {
  outline: none;
  border-color: var(--jsf-accent);
  box-shadow: var(--jsf-focus);
}

form input:disabled,
form select:disabled,
form textarea:disabled {
  background: var(--jsf-surface-2);
  color: var(--jsf-muted);
  cursor: not-allowed;
}

/* Invalid state (fires after the user has interacted) */
form input:user-invalid,
form select:user-invalid,
form textarea:user-invalid {
  border-color: var(--jsf-danger);
}

form textarea {
  min-height: 6em;
  resize: vertical;
}

form select[multiple] {
  min-height: 7.5em;
  padding: 4px;
}

form select[multiple] option {
  padding: 6px 10px;
  border-radius: var(--jsf-radius-sm);
}

/* --- range --- */
form [data-widget="range"] input {
  padding: 0;
  border: none;
  box-shadow: none;
  background: transparent;
  accent-color: var(--jsf-accent);
}

/* --- checkbox / radio (single boolean row) ---
   Row has <input> then <label>; render them inline. */
form [data-widget="checkbox"][data-type="boolean"] {
  flex-direction: row;
  align-items: center;
  gap: 8px;
}

form [data-widget="checkbox"][data-type="boolean"] > input {
  width: auto;
  margin: 0;
  box-shadow: none;
  accent-color: var(--jsf-accent);
}

form [data-widget="checkbox"][data-type="boolean"] > label {
  font-weight: 500;
  margin: 0;
}

/* --- enum groups: radios or multi-checkboxes --- */
form fieldset[data-variant="group"] {
  border: 1px solid var(--jsf-border);
  border-radius: var(--jsf-radius);
  background: var(--jsf-surface);
  padding: 10px 14px 12px;
  box-shadow: var(--jsf-shadow);
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
}

form fieldset[data-variant="group"] > legend {
  padding: 0 4px;
  margin-left: -4px;
}

form fieldset[data-variant="group"] > label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  padding: 4px 8px;
  border-radius: var(--jsf-radius-sm);
  font-weight: 500;
  cursor: pointer;
  user-select: none;
  transition: background 120ms ease;
}

form fieldset[data-variant="group"] > label:hover {
  background: var(--jsf-accent-bg);
}

form fieldset[data-variant="group"] > label > input {
  width: auto;
  margin: 0;
  box-shadow: none;
  accent-color: var(--jsf-accent);
}

/* Horizontal flow for enum groups with 4 or fewer visible children */
form fieldset[data-variant="group"] {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
}

form fieldset[data-variant="group"] > legend {
  flex-basis: 100%;
  margin-bottom: 2px;
}

form fieldset[data-variant="group"] > small {
  flex-basis: 100%;
}

/* --- nested object blocks --- */
form fieldset[data-variant="object"] {
  border: 1px solid var(--jsf-border);
  border-radius: var(--jsf-radius);
  background: var(--jsf-surface-2);
  padding: 14px 16px 16px;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

form fieldset[data-variant="object"] > legend {
  padding: 0 6px;
  margin-left: -6px;
  font-size: 0.9375rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--jsf-muted);
}

/* --- submit buttons placed alongside the form --- */
form button[type="submit"] {
  align-self: flex-start;
  background: var(--jsf-accent);
  color: white;
  border: 1px solid var(--jsf-accent);
  border-radius: var(--jsf-radius-sm);
  padding: 10px 18px;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--jsf-shadow);
  transition: filter 120ms ease, transform 60ms ease;
}

form button[type="submit"]:hover {
  filter: brightness(1.05);
}

form button[type="submit"]:active {
  transform: translateY(1px);
}
Rendered HTML (same as baseline)
<form method="post" action="#">
  <div data-name="email" data-type="string" data-widget="email" data-required="true">
    <label for="email">Email</label>
    <input id="email" name="email" type="email" required="" aria-required="true" aria-describedby="email-hint"/>
    <small id="email-hint">Used for sign-in and account recovery.</small>
  </div>
  <div data-name="handle" data-type="string" data-widget="text" data-required="true">
    <label for="handle">Handle</label>
    <input id="handle" name="handle" type="text" required="" aria-required="true" aria-describedby="handle-hint" minlength="3" maxlength="24" pattern="^[a-zA-Z0-9_]+$" title="^[a-zA-Z0-9_]+$"/>
    <small id="handle-hint">3-24 letters, numbers, or underscores.</small>
  </div>
  <div data-name="homepage" data-type="string" data-widget="url">
    <label for="homepage">Homepage</label>
    <input id="homepage" name="homepage" type="url" aria-describedby="homepage-hint"/>
    <small id="homepage-hint">Optional — link to your site or profile.</small>
  </div>
  <div data-name="plan" data-type="string" data-widget="select" data-required="true">
    <label for="plan">Plan</label>
    <select id="plan" name="plan" required="" aria-required="true" aria-describedby="plan-hint">
      <option value="free">free</option>
      <option value="pro" selected="">pro</option>
      <option value="team">team</option>
    </select>
    <small id="plan-hint">You can upgrade or downgrade any time.</small>
  </div>
  <fieldset data-variant="group" data-name="billing" data-type="string" data-widget="radio">
    <legend>Billing cycle</legend>
    <label>
      <input type="radio" name="billing" value="monthly"/>
      <span>monthly</span>
    </label>
    <label>
      <input type="radio" name="billing" value="yearly" checked=""/>
      <span>yearly</span>
    </label>
  </fieldset>
  <div data-name="seats" data-type="integer" data-widget="range">
    <label for="seats">Seats</label>
    <input id="seats" name="seats" type="range" aria-describedby="seats-hint" value="5" min="1" max="50" step="1"/>
    <small id="seats-hint">Only relevant for team plans.</small>
  </div>
  <div data-name="notifications" data-type="boolean" data-widget="checkbox">
    <input id="notifications" name="notifications" type="checkbox" checked=""/>
    <label for="notifications">Email me product updates</label>
  </div>
  <fieldset data-variant="group" aria-required="true" aria-describedby="topics-hint" data-name="topics" data-type="array" data-widget="checkbox" data-required="true">
    <legend>Topics you care about</legend>
    <label>
      <input type="checkbox" name="topics" value="frontend" checked=""/>
      <span>frontend</span>
    </label>
    <label>
      <input type="checkbox" name="topics" value="backend" checked=""/>
      <span>backend</span>
    </label>
    <label>
      <input type="checkbox" name="topics" value="design"/>
      <span>design</span>
    </label>
    <label>
      <input type="checkbox" name="topics" value="ops"/>
      <span>ops</span>
    </label>
    <small id="topics-hint">Pick at least one.</small>
  </fieldset>
  <div data-name="channels" data-type="array" data-widget="select">
    <label for="channels">Notification channels</label>
    <select id="channels" name="channels" multiple="">
      <option value="email" selected="">email</option>
      <option value="in-app" selected="">in-app</option>
      <option value="sms">sms</option>
      <option value="push">push</option>
      <option value="rss">rss</option>
    </select>
  </div>
  <fieldset data-variant="object" data-name="profile" data-type="object" data-widget="object">
    <legend>Profile</legend>
    <div data-name="profile.name" data-type="string" data-widget="text">
      <label for="profile-name">Full name</label>
      <input id="profile-name" name="profile.name" type="text" minlength="1"/>
    </div>
    <div data-name="profile.bio" data-type="string" data-widget="textarea">
      <label for="profile-bio">Short bio</label>
      <textarea id="profile-bio" name="profile.bio" aria-describedby="profile-bio-hint" maxlength="280">
      </textarea>
      <small id="profile-bio-hint">Up to 280 characters.</small>
    </div>
  </fieldset>  <button type="submit">Create account</button>
</form>