Stylování formulářových prvků

Formulářové prvky mají ve všech prohlížečích předdefinovaný styl (podle OS) a je obtížné je změnit. Přitom požadavek na změnu vzhledu formulářů je jeden z nejčastějších při vývoji RIA (Rich Internet Application) nebo PWA (Progressive Web App).

Původní text: listopad 2017
Aktualizováno: březen 2022

File input

Prvek pro nahrání souboru je jeden z nejobtížněji stylovatelných. Přesněji řečeno ve většině prohlížečů se vůbec stylovat nedá, vyjma rozměrů a některých vlastností, které vzhled přímo neovlivňují a jen mění, jak a kde se ve stránce vykreslí.

Toho se dá ale využít společně s faktem, že label (popisek) přiřazený prvky vždy vyvolá focus propojeného prvku. Pokud tedy skryjete samotný formulářový prvek pro upload a následně k němu připojíte label, budete ho moci dle libosti stylovat a zachováte možnost soubor vybrat a nahrát.

Jedinou nevýhodou je to, že formulářový prvek nemůžete úplně skrýt (display: none), protože by přestal fungovat. Skrytí ale lze dosáhnout právě pomocí CSS vlastností, které prvek vizuálně skryjí před uživateli, ale zároveň zachovají jeho funkčnost.

<label for=upload>
    <input type=file id=upload>
</label>

Je jedno, jestli input vložíte do labelu nebo někam jinam. Tenhle zápis je ale z hlediska organizace HTML zpravidla nejlepší (při debuggování stačí najít label a víte, že input je v něm).

label[for=upload] {
    //... libovolný styl, jak je potřeba
}
input[type=file] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0,0,0,0);
    border: 0;
}

Nejjednodušší metoda skrytí prvku by byla nastavení absolutního pozicování a posunutí někam hodně daleko pryč. Nevýhodou tohoto řešení je, že prohlížeč musí vytvořit dostatečně velkou stránku, aby posun pojmul, což ho zbytečně zatěžuje.

Další řešení je nastavit viditelnost nebo průhlednost, což také prvek skryje. Problém ale je, že některé prohlížeče mohou neviditelný nebo zcela průhledný prvek považovat za skrytý a vypnout ho.

Nejlepší řešení je tedy využít vlastnost clip: rect(), která prohlížeči říká, která část prvku se má vykreslit. Pokud zadáte samé nuly, znamená to, že prvek se nezobrazí vůbec. Vlastnost clip ale funguje tak, že prvek nejprve celý vykreslí (takže bude fungovat), a pak teprve vybere, kterou část vykreslí (a když řekneme, že žádnou, prostě ho nechá skrytý, ale funkční.

Vlastnost clip podporují prakticky všechny prohlížeče novější než IE7 a Opera 6 (takže v roce 2022+ to lze považovat za standard).

Select

Select (seznam) můžete celkem dobře stylovat ve většině prohlížečů s drobnými rozdíly a nedostatky.

U selectu vždy používejte přímo daný prvek místo toho, abyste ho nahrazovali nějakým vlastním inputem s rozbalovacím menu. U klasického selectu totiž dokáží mobilní zařízení zobrazit vlastní implementaci, která zabrání např. vytečení seznamu z obrazu a usnadňují výběr pomocí dotykového ovládání.

Pokud chcete stylovat položky v rozbaleném seznamu, musíte některé styly nastavit celému selectu a některé jen jeho option.

Pokud chcete vycentrovat text selectu, nefunguje na to vlastnost text-align, ale text-align-last, která je podporována od IE 5.5 a všech ostatních prohlížečích kromě Safari.

Pro další odsazení textu pak můžete normálně použít padding, ale je potřeba pamatovat na to, že se do něj nepočítá ikona šipky a proto je vždy potřeba nechat vpravo větší okraj:

select {
    /* nastavíme odsazení textu a zajistíme,
       aby text nelezl do ikony (16px) */
    padding: 2px 20px 2px 4px;
}

Rozbalený seznam automaticky přebírá levý padding selektu, takže zobrazí položky stejně odsazené.

Jednotlivé option můžete v seznamu vypnout (disable), takže pak nepůjdou vybrat, nebo je můžete úplně skrýt (display: none). Skryté option jdou stále nastavit programově, takže můžete do selectu vybrat hodnotu, která v seznamu není vidět. Např. můžete takto doplnit text pro prázdnou hodnotu.

Aktualizace: Novější verze Webkit a Chromium někdy zobrazují options nastavené na display: none stejně jako disabled. Pokud tedy chcete některé položky skutečně skrýt (a ne jen vypnout), je potřeba použít Javaskript a prvky fyzicky ze seznamu odstranit!

select > option[value=''] {
        /* prázdná hodnota půjde vyplnit
           programově, zobrazí svůj text,
           ale uživatel ji vybrat nemůže */
        display: none;
}

Pro skrytí prvků, resp. vybrání určitých prvků na základě jiného selektu nebo prvku můžete použít obdobný skript (zde příklad se zobrazením krajů pro zvolený stát:

var regions = ['CZ' => ['Praha', ...], 'DE' => ['Berlin', ...], ...];

$('#country').off('change.region').on('change.region', function () {
	var
		country = $(this).val(),
		$region = $('#region'),
		region = $region.val(),
        //načtení dříve odstraněných krajů
		$allRegions = $region.data('regions') || $(),
		$regions = $region.find('option').not('[value=""]'),
		reset = true;
	;

    //vloží aktuálně zobrazené regiony do skrytého seznamu
	$allRegions = $allRegions.add($regions.detach());
    //uloží seznam všech regionů pro pozdější použití
	$region.data('regions', $allRegions);

    //Vybere regiony pro aktuální stát a vloží je do selektu
	$.each(regions[country], function() {
		$allRegions.filter('option[value='+this+']').appendTo($region);
		if (region == this) {
            //pokud je aktuálně vybraný region z vybraného státu, ...
            // ... není potřeba s ním nic dělat
			reset = false;
		}
	});

    //pokud je aktuálně vybraný region z jiného státu, ...
    // ... je potřeba vymazat hodnotu selektu...
    // ... a donutit tak uživatele zvolit platný kraj
	if (reset) {
		$region.val('');
	}

    //vypne selekt, pokud zvolený stát neobsahuje žádné kraje
    // (např. pokud máme mezi státy Vatikán)
	$region.prop('disabled',
        $region.find('option:not([value=""]):first')
            .length ? null : 'disabled'
    );

//Při prvním načtení profiltruje regiony
// (skript očekává, že všechny option...
// ... budou vloženy do selektu ..
// ... pro případ, že je JS zakázané)
}).triggerHandler('change.region');

Pokud potřebujete selekt nastylovat víc, než umožňuje prohlížeč (např. v něm chcete zobrazit dvě řádky textu nebo ikony), můžete použít zástupný prvek, přes který pak zobrazíte průhledný select:

.select-wrapper {
    position: relative;
}
.select-wrapper > select {
    position: absolute;
    z-index: 10;
    top: 0;
    right: 0;
    left: 0;
    visibility: visible;
    opacity: .0001;
    width: 100%;
    height: 100%;
    padding: 0;
    margin: 0;
    border: 0;
}

Když použijete tento styl, můžete do select-wrapper přidat libovolné HTML prvky a vlastní CSS, které pak bude zobrazeno jako váš selekt. Přitom ale vlastní hodnota bude stále uložena a změnitelná pomocí původního select prvku, který celý wrapper překrývá a reaguje na kliknutí. Díky nastavení opacity není vidět a je zcela průhledný, ale stále se chová jako viditelný prvek.

Select menu

Selekt menu je nový prvek, který od roku 2022 připravují týmy Microsoftu (Edge) a Googlu (Chrome). Ve výchozím stavu by mělo fungovat stejně jako starý select:

<selectmenu>
    <option value=1>Option 1</option>
    <option value=1>Option 1</option>
</selectmenu>

Na rozdíl od selektu ale selektmenu může obsahovat další HTML prvky (obdobně jako třeba <picture> nebo <figure>), pomocí nichž můžete určovat vzhled jak samotného prvku tak i rozbalovacího seznamu:

<selectmenu>
    <div class=visible-part slot=button>
        <button behavior=button>
             <label>Hodnota:</label>
             <span behavior=selected-value slot=selected-value>
                 Výchozí
             </span>
        </button>
    <div class="list-part" slot=listbox>
        <popup behavior=listbox>
            <h3>Seznam hodnot</h3>
            <option value=1>Option 1</option>
            <option value=1>Option 1</option>
        </popup>
</selectmenu>

Selekt menu má několik částí, které jsou volitelné, ale je potřeba je zapsat s konkrétními vlastnostmi:

  • libovolný prvek s vlastností slot=button definuje viditelnou část prvku. To odpovídá tedy „input“ části stávajícího selektu.
  • prvek <button> s vlastností behavior=button definuje klikatelnou část, která zobrazí seznam. To odpovídá šipce u selektu.
  • do libovolného prvku s vlastnostmi behavior=selected-value slot=selected-value se bude automaticky vyplňovat zvolená hodnota. Pokud není nastaven, použije se prvek <button> nebo prvek s vlastností slot=button.
  • libovolný prvek s vlastností slot=listbox definuje část, která ke skrytá a zobrazí se po kliku na button.
  • prvek popup s vlastností behavior=listbox definuje seznam hodnot. Může obsahovat option a optgroup stejně jako stávající selekt, ale také jakékoliv jiné prvky (DIV, Hx, HR, atd.)

Formátování

Díky tomu, že selektmenu může obsahovat řadu vnořených prvků, můžete je díky tomu plně formátovat v CSS použitím tagů a vlastností:

selectmenu {
    //... základní styl - primárně určený pro zapnutí Flexboxu nebo Gridu
}
[slot=button] {
    // ... styl pro viditelnou část
}
[slot=button} > button {
    // ... styl pro klikatelnou část
}
[slot=listbox] {
    // ... styl pro rozbalovací část
    // opět primárně pro zapnutí Flexboxu nebo Gridu
}
[slot=listbox] > popup {
    // ... styl pro viditelnou rozbalovací část
}
[slot=listbox] > popup option {
    // ... styl pro jednotlivé hodnoty
}

Použití

Aktuálně (březen 2022) je selektmenu dostupné pro otestování v nejnovější verzi Chrome poté, co zapnete „Experimentální funkce“.

Dá se očekávat, že na základě ohlasů se bude ještě specifikace měnit, takže skutečný význam vlastností behavior a slot se může v budoucnu změnit (berte tedy zde uvedené skutečnosti s rezervou).

Osobně by mě například zajímalo, jak je to s vybranou hodnotou. Jestli třeba prvek s vlastností slot=selected-value bude svůj obsah měnit podle obsahu zvolené hodnoty zatímco prvek s vlastností behavior=selected-value bude měnit svůj stav :empty podle toho, jestli je nebo není vybrána hodnota. Stejně tak jestli je po vybrání hodnoty obsah prvku se slot=selected-value zachová v DOMu a znovu se zobrazí jako placeholder po vybrání prázdné hodnoty (např. po kliku na Reset).

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *

Tato stránka používá Akismet k omezení spamu. Podívejte se, jak vaše data z komentářů zpracováváme..