// htmltahak/ · 07 z 09

Formuláře

form, input (všechny typy!), label, select, textarea, button, fieldset, legend — kompletní průvodce HTML formuláři s důrazem na přístupnost a UX.

<form>

Formulář

Kontejner pro formulářové prvky. action = URL odesílání, method = GET/POST.

BLOCK

form obaluje formulářové prvky a definuje jak se data odešlou. action = URL pro odeslání. method = HTTP metoda. GET = data v URL (vyhledávání, sdílitelné). POST = data v těle requestu (citlivá data, upload).

// JavaScript AJAX odesláníModerní formuláře zachytí submit event, zavolají e.preventDefault() a odešlou data přes fetch(). FormData objekt automaticky serializuje celý formulář. Lepší UX než plný refresh stránky.
Atributy
actionURL pro odeslání formulářových datURL
methodGET = URL params (vyhledávání), POST = těloGET / POST
enctypePro file upload: multipart/form-datamultipart/form-data
novalidateVypne HTML5 validaci (pro vlastní JS)boolean
autocompleteZapnout/vypnout autocomplete prohlížečeon / off
  • GET pro vyhledávání (URL = sdílitelné a záložkovátelné)
  • POST pro přihlášení, objednávky, upload
  • enctype="multipart/form-data" povinné pro upload souborů
  • novalidate + vlastní JS validace = lepší UX
Příklady
HTML + JS
<!-- Klasický POST formulář -->
<form action="/kontakt" method="POST">
  <label for="jmeno">Jméno:</label>
  <input type="text" id="jmeno"
    name="jmeno" required>
  <button type="submit">Odeslat</button>
</form>

<!-- AJAX odeslání -->
<form id="myForm" novalidate>
  <!-- pole... -->
  <button type="submit">Odeslat</button>
</form>

<script>
document.getElementById('myForm')
  .addEventListener('submit', async (e) => {
    e.preventDefault();
    const data = new FormData(e.target);

    try {
      const res = await fetch('/api/kontakt', {
        method: 'POST',
        body: data
      });
      if (res.ok) alert('Odesláno!');
    } catch (err) {
      console.error(err);
    }
  });
</script>

<!-- Upload souboru -->
<form method="POST"
  enctype="multipart/form-data">
  <input type="file" name="foto">
  <button type="submit">Nahrát</button>
</form>
<input>

Vstupní pole

Nejpoužívanější formulářový prvek. Void element. Atribut type mění vše — validaci, klávesnici, UX.

VOIDINLINE

Správný type = lepší mobilní klávesnice + vestavěná validace + správné UX. Například: type="email" → na mobilu klávesnice s @. type="tel" → numerická klávesnice. type="date" → nativní datový picker.

// Proč type záležítype="number" na mobilu = numerická klávesnice. type="email" = klávesnice s @ a .com. type="tel" = telefonní klávesnice. Bez správného type uživatel vidí vždy stejnou QWERTY klávesnici — horší UX.
Přehled všech typů
typeK čemuSpeciální chování
textLibovolný text (výchozí)
emailEmailová adresaValiduje formát, @ klávesnice
passwordHesloSkryje znaky tečkami
numberČísloŠipky nahoru/dolů, min/max/step
telTelefonní čísloNumerická klávesnice na mobilu
urlWebová adresaValiduje formát URL
searchVyhledávací poleKřížek pro smazání
dateDatumNativní datový picker
timeČasNativní výběr času
datetime-localDatum + časKombinovaný picker
monthMěsíc + rokPicker měsíce
weekTýden v rocePicker týdne
checkboxZaškrtávací políčkochecked atribut, boolean
radioPřepínač (výběr jednoho)Stejné name = skupina
rangePosuvníkmin/max/step, value = výchozí
colorVýběr barvyColor picker prohlížeče
fileUpload souboru/ůaccept, multiple
hiddenSkryté poleNeviditelné, odesílá se s formulářem
submitOdesílací tlačítkoRaději <button type="submit">
resetReset formulářeVymaže všechna pole
buttonTlačítko bez akceRaději <button type="button">
imageObrázkové submit tlačítkosrc + alt povinné
Klíčové atributy
typeTyp pole — viz tabulka vlevostring
nameKlíč v odesílaných datech. Povinné!string
idPropojení s label[for]. Musí být unikátní.string
valueVýchozí hodnota nebo hodnota pro submitstring
placeholderNápověda (mizí při psaní — nenahrazuje label!)string
requiredPovinné pole — neodesle se bez hodnotyboolean
disabledZakáže pole (neodesílá se!)boolean
readonlyJen pro čtení (ale odesílá se)boolean
min / maxRozsah pro number, date, rangečíslo / datum
stepKrok pro number, rangečíslo
patternRegex pro validaci texturegex
minlength / maxlengthMin/max délka textučíslo
autocompleteNapovídání prohlížečeon/off/email/name
acceptPro file: povolené typy souborů.jpg,.pdf,image/*
multipleVíce hodnot (file, email)boolean
checkedVýchozí stav checkbox/radioboolean
Příklady
HTML
<!-- Text -->
<input type="text" name="jmeno"
  placeholder="Vaše jméno" required>

<!-- Email -->
<input type="email" name="email"
  autocomplete="email">

<!-- Heslo s min délkou -->
<input type="password" name="heslo"
  minlength="8" required>

<!-- Číslo s rozsahem -->
<input type="number" name="mnozstvi"
  min="1" max="99" value="1">

<!-- Datum -->
<input type="date" name="datum"
  min="2024-01-01">

<!-- Checkbox -->
<input type="checkbox" id="souhlas"
  name="souhlas" required>
<label for="souhlas">
  Souhlasím s podmínkami
</label>

<!-- Radio skupina -->
<input type="radio" id="muz"
  name="gender" value="muz">
<label for="muz">Muž</label>

<input type="radio" id="zena"
  name="gender" value="zena">
<label for="zena">Žena</label>

<!-- Upload (jen obrázky) -->
<input type="file" name="foto"
  accept="image/*" multiple>

<!-- Slider -->
<input type="range" name="hlasitost"
  min="0" max="100" value="50">

<!-- Barva -->
<input type="color" name="barva"
  value="#F75B9E">

<!-- Skryté pole (CSRF token) -->
<input type="hidden" name="csrf"
  value="abc123xyz">

<!-- Pattern validace (IČO 8 číslic) -->
<input type="text" name="ico"
  pattern="[0-9]{8}"
  title="IČO musí mít 8 číslic">
<label>

Popisek pole

Propojí textový popis s formulářovým polem. Povinné pro přístupnost. Klik na label = focus na pole.

INLINE

label propojí popis s formulářovým polem. Dvě metody: for="id-pole" nebo obalení pole labellem. Klik na label = focus/check na poli — větší klikatelná plocha. Bez label je formulář nepřístupný pro screen readery.

// Proč label záležíScreen reader při focusu pole přečte text labelu. Bez labelu uživatel neví, co má vyplnit. Placeholder mizí při psaní — není náhrada za label. Checkbox bez labelu = malá klikatelná plocha (špatné UX).
Atributy
forMusí odpovídat id vstupního poleid-pole
Alternativy (jen když label nelze)
aria-label na input (neviditelný) aria-labelledby="id-jiného-elementu" title na input (tooltip, méně přístupné)
  • for = id pole (ne name!)
  • placeholder NENAHRAZUJE label
  • Skrytý label: class="sr-only" (viditelný jen pro screen readery)
  • Povinné pole: <span aria-hidden="true">*</span>
Dvě metody propojení
HTML
<!-- Metoda 1: for + id -->
<label for="email">Email:</label>
<input type="email"
  id="email" name="email">

<!-- Metoda 2: obalení (bez id) -->
<label>
  Heslo:
  <input type="password" name="heslo">
</label>

<!-- Checkbox — obalení = velká plocha -->
<label>
  <input type="checkbox" name="novinky">
  Přihlásit k odběru novinek
</label>

<!-- Povinné pole -->
<label for="email">
  Email
  <span aria-hidden="true"
    style="color:red">*</span>
</label>
<input type="email" id="email"
  required aria-required="true">

<!-- Skrytý label (visually hidden) -->
<label for="search" class="sr-only">
  Hledat na webu
</label>
<input type="search" id="search"
  placeholder="Hledat...">

<style>
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}
</style>
<label for="email"> + <input id="email">
<input placeholder="Email"> bez label
<select>

Rozbalovací seznam

Výběr z předdefinovaných možností. option = položka, optgroup = kategorie skupin.

INLINE

select je rozbalovací seznam. Uvnitř jsou <option> elementy. Pro skupiny použijte <optgroup>. multiple umožní vybrat více položek (Ctrl+klik). Pro 2–4 možnosti zvažte radio buttons — lepší UX.

// Select vs radio buttonsSelect je vhodný pro 5+ možností (krajé, státy, kategorie). Pro 2–4 možnosti jsou radio buttons přehlednější — uživatel vidí všechny najednou bez klikání. Na mobilu select = nativní picker.
Atributy
nameKlíč v odesílaných datechstring
multipleVícenásobný výběr (Ctrl+klik)boolean
sizePočet viditelných položek najednoučíslo
requiredPovinný výběrboolean
disabledZakáže selectboolean
  • Prázdná option jako placeholder: value=""
  • optgroup pro logické skupiny možností
  • CSS: appearance: none + vlastní šipka
  • Pro vyhledávání v dlouhém seznamu: datalist nebo combobox
Příklady
HTML
<!-- Základní select -->
<label for="kraj">Kraj:</label>
<select id="kraj" name="kraj" required>
  <option value="">
    -- Vyberte kraj --
  </option>
  <optgroup label="Čechy">
    <option value="PH">Praha</option>
    <option value="JC">Jihočeský</option>
    <option value="JM" selected>
      Jihomoravský
    </option>
  </optgroup>
  <optgroup label="Morava">
    <option value="MS">
      Moravskoslezský
    </option>
  </optgroup>
</select>

<!-- Multiple výběr -->
<label for="jazyky">Jazyky:</label>
<select id="jazyky" name="jazyky[]"
  multiple size="4">
  <option value="cs">Čeština</option>
  <option value="en">Angličtina</option>
  <option value="de">Němčina</option>
  <option value="sk">Slovenština</option>
</select>
<small>Ctrl+klik = více možností</small>

<!-- Vlastní stylování -->
<style>
select {
  appearance: none;
  background-image: url("data:image/svg+xml,...");
  background-repeat: no-repeat;
  background-position: right 0.75rem center;
  padding-right: 2rem;
}
</style>
<textarea>

Víceřádkový text

Víceřádkové textové pole. Výchozí hodnota je obsah tagu. CSS resize řídí změnu velikosti.

INLINE

textarea je víceřádkové textové pole. Výchozí hodnota je obsah elementu (ne atribut value jako u input!). Uživatel může tahat za roh a měnit velikost — ovládejte CSS resize.

// rows a cols vs CSSrows a cols jsou fallback pro prohlížeče bez CSS. Reálnou velikost nastavte CSS width a height nebo min-height. resize: vertical = uživatel může jen vertikálně zvětšovat (nejlepší UX).
Atributy
nameKlíč v datechstring
rowsPočet viditelných řádků (fallback)číslo
colsPočet viditelných sloupců (fallback)číslo
requiredPovinné poleboolean
maxlengthMax počet znakůčíslo
minlengthMin počet znakůčíslo
placeholderNápověda (mizí při psaní)string
readonlyJen pro čtení (ale odesílá se)boolean
  • Výchozí text je UVNITŘ tagu, ne v atributu!
  • resize: vertical nebo none CSS
  • Počítadlo znaků: maxlength + JS counter
  • Auto-resize výšky: JS scrollHeight trick
Příklady
HTML + JS
<!-- Základní textarea -->
<label for="zprava">Zpráva:</label>
<textarea
  id="zprava"
  name="zprava"
  rows="5"
  required
  maxlength="1000"
  placeholder="Napište nám..."
></textarea>

<!-- Textarea s počítadlem znaků -->
<label for="popis">Popis:</label>
<textarea id="popis" name="popis"
  maxlength="500"
  oninput="updateCount(this)"
></textarea>
<small id="count">0 / 500</small>

<script>
function updateCount(el) {
  document.getElementById('count')
    .textContent =
    `${el.value.length} / ${el.maxLength}`;
}
</script>

<!-- Auto-resize výšky -->
<textarea id="autosize"
  oninput="this.style.height='auto';
    this.style.height=this.scrollHeight+'px'"
  style="overflow:hidden"
></textarea>

<!-- CSS -->
<style>
textarea {
  width: 100%;
  resize: vertical; /* nebo: none */
  min-height: 100px;
  font-family: inherit;
  font-size: 1rem;
}
</style>
<button>

Tlačítko

type="submit" odesílá formulář (výchozí!), type="button" pro JS akce, type="reset" resetuje.

INLINE

button je klikatelný prvek. Vždy explicitně nastavte type! Výchozí je submit — button bez type uvnitř formuláře formulář odešle. button může obsahovat HTML (text + ikona). Preferujte button před <input type="button">.

// button vs a<button> = akce (submit, toggle, smazat). <a> = navigace (přejít na stránku). Nikdy nepoužívejte <a href="#"> nebo <div onclick> jako tlačítko — prohlížeč nepovažuje za interaktivní prvek, klávesnice ho nenajde.
Atributy
typesubmit = odešle form (DEFAULT!), button = nic, reset = vymažesubmit/button/reset
disabledZakáže tlačítko (neodesílá se)boolean
name / valuePro identifikaci tlačítka při odeslánístring
formID formuláře mimo DOM stromemform-id
formactionPřepsat action formuláře pro toto tlačítkoURL
aria-labelPopis pro screen readery (ikonová tlačítka)string
aria-pressedToggle tlačítko: true/falsetrue/false
  • Vždy explicitní type="submit/button/reset"
  • Ikonová tlačítka: aria-label povinné!
  • Loading stav: disabled + "Odesílám..."
  • button může obsahovat span, svg, img uvnitř
Příklady
HTML + JS
<!-- Submit -->
<button type="submit">Odeslat</button>

<!-- JS akce (type="button" nutné!) -->
<button type="button"
  onclick="toggleMenu()">
  Menu ☰
</button>

<!-- Ikonové tlačítko -->
<button type="button"
  aria-label="Smazat položku">
  <svg aria-hidden="true">
    <use href="#icon-delete"></use>
  </svg>
</button>

<!-- Loading stav -->
<button type="submit" id="submitBtn">
  Odeslat
</button>

<script>
document.querySelector('form')
  .addEventListener('submit', (e) => {
    const btn = document.getElementById(
      'submitBtn'
    );
    btn.disabled = true;
    btn.textContent = 'Odesílám...';
  });
</script>

<!-- Toggle tlačítko -->
<button
  type="button"
  aria-pressed="false"
  id="darkBtn"
  onclick="
    const p = this.getAttribute('aria-pressed');
    this.setAttribute('aria-pressed', p !== 'true');
  "
>
  Tmavý režim
</button>

<!-- Dvě submit tlačítka, různé akce -->
<button type="submit"
  formaction="/ulozit">
  Uložit
</button>
<button type="submit"
  formaction="/publikovat">
  Publikovat
</button>
<button type="button" onclick="fn()">
<a href="#" onclick="fn()"> jako tlačítko
<button> bez type v formuláři = submit!
<fieldset> & <legend>

Skupina polí

fieldset = sémanticky seskupí related pole. legend = název skupiny. Povinné pro radio a checkbox skupiny.

BLOCK

fieldset vizuálně a sémanticky seskupí related formulářová pole. legend je název skupiny — screen reader přečte legend při focusu každého pole uvnitř. Povinné pro radio buttons a checkbox skupiny — bez fieldset+legend screen reader nezná kontext.

// Proč fieldset pro radioUživatel slyší: "Pohlaví — Muž" (legend + label). Bez fieldset: jen "Muž" — chybí kontext. Pro radio skupinu je fieldset+legend nejdůležitější přístupnostní vzor formuláře.
Atributy fieldset
disabledZakáže všechna pole uvnitř najednouboolean
nameNázev skupinystring
formID formuláře (pro fieldset mimo form)form-id
  • legend musí být PRVNÍ child fieldset
  • disabled fieldset zakáže všechna pole najednou
  • CSS: fieldset { border: none; padding: 0; } pro vlastní styl
  • legend nelze jednoduše stylovat — use span uvnitř
Příklady
HTML
<!-- Radio skupina -->
<fieldset>
  <legend>Pohlaví</legend>
  <label>
    <input type="radio"
      name="gender" value="muz">
    Muž
  </label>
  <label>
    <input type="radio"
      name="gender" value="zena">
    Žena
  </label>
  <label>
    <input type="radio"
      name="gender" value="jine">
    Jiné
  </label>
</fieldset>

<!-- Adresová sekce -->
<fieldset>
  <legend>Doručovací adresa</legend>
  <label for="ulice">Ulice:</label>
  <input type="text" id="ulice"
    name="ulice" autocomplete="street-address">
  <label for="mesto">Město:</label>
  <input type="text" id="mesto"
    name="mesto" autocomplete="address-level2">
</fieldset>

<!-- Disabled fieldset -->
<fieldset disabled>
  <legend>Fakturační údaje</legend>
  <!-- všechna pole zakázána -->
  <input type="text" name="firma">
</fieldset>

<!-- CSS: bez rámečku -->
<style>
fieldset {
  border: none;
  padding: 0;
  margin: 0 0 1.5rem;
}
legend {
  font-weight: 700;
  margin-bottom: 0.75rem;
}
</style>
← 06 Média 07 / 09 — Formuláře 08 Tabulky →