Resumen
Un <howto-checkbox>
representa una opción booleana en un formulario. El tipo más común de casilla de verificación es un tipo doble que permite al usuario alternar entre dos opciones: marcada y desmarcada.
El elemento intenta autoaplicar los atributos role="checkbox"
y tabindex="0"
cuando se crea por primera vez. El atributo role
ayuda a la tecnología de accesibilidad, como un lector de pantalla, a indicarle al usuario qué tipo de control es. El atributo tabindex
incluye el elemento en el orden de tabulación, lo que permite que se enfoque y opere con el teclado. Para obtener más información sobre estos dos temas, consulta ¿Qué puede hacer ARIA? y Cómo usar tabindex.
Cuando la casilla de verificación está marcada, se agrega un atributo booleano checked
y se establece una propiedad checked
correspondiente en true
. Además, el elemento establece un atributo aria-checked
en "true"
o "false"
, según su estado. Si haces clic en la casilla de verificación con un mouse o una barra espaciadora, se activan o desactivan estos estados marcados.
La casilla de verificación también admite un estado disabled
. Si la propiedad disabled
se establece como verdadera o se aplica el atributo disabled
, la casilla de verificación establece aria-disabled="true"
, quita el atributo tabindex
y vuelve a enfocar el documento si la casilla de verificación es el activeElement
actual.
La casilla de verificación se vincula con un elemento howto-label
para garantizar que tenga un nombre accesible.
Referencia
- Instructivo: Componentes en GitHub
- Patrón de casilla de verificación en las Prácticas de autoría de ARIA 1.1
- ¿Qué puede hacer ARIA?
- Cómo usar tabindex
Demostración
Ver la demostración en vivo en GitHub
Ejemplo de uso
<style>
howto-checkbox {
vertical-align: middle;
}
howto-label {
vertical-align: middle;
display: inline-block;
font-weight: bold;
font-family: sans-serif;
font-size: 20px;
margin-left: 8px;
}
</style>
<howto-checkbox id="join-checkbox"></howto-checkbox>
<howto-label for="join-checkbox">Join Newsletter</howto-label>
Código
(function() {
Define códigos de teclas para ayudar a controlar los eventos del teclado.
const KEYCODE = {
SPACE: 32,
};
Clonar el contenido de un elemento <template>
tiene un mejor rendimiento que usar innerHTML, ya que evita costos adicionales de análisis de HTML.
const template = document.createElement('template');
template.innerHTML = `
<style>
:host {
display: inline-block;
background: url('../images/unchecked-checkbox.svg') no-repeat;
background-size: contain;
width: 24px;
height: 24px;
}
:host([hidden]) {
display: none;
}
:host([checked]) {
background: url('../images/checked-checkbox.svg') no-repeat;
background-size: contain;
}
:host([disabled]) {
background:
url('../images/unchecked-checkbox-disabled.svg') no-repeat;
background-size: contain;
}
:host([checked][disabled]) {
background:
url('../images/checked-checkbox-disabled.svg') no-repeat;
background-size: contain;
}
</style>
`;
class HowToCheckbox extends HTMLElement {
static get observedAttributes() {
return ['checked', 'disabled'];
}
El constructor del elemento se ejecuta cada vez que se crea una instancia nueva. Las instancias se crean analizando HTML, llamando a document.createElement('howto-checkbox') o llamando al nuevo HowToCheckbox(). El constructor es un buen lugar para crear shadow DOM, aunque debes evitar tocar atributos o elementos secundarios del Light DOM, ya que es posible que aún no estén disponibles.
constructor() {
super();
this.attachShadow({mode: 'open'});
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
connectedCallback()
se activa cuando se inserta el elemento en el DOM. Es un buen lugar para establecer el role
, el tabindex
, el estado interno y los objetos de escucha de eventos de instalación iniciales.
connectedCallback() {
if (!this.hasAttribute('role'))
this.setAttribute('role', 'checkbox');
if (!this.hasAttribute('tabindex'))
this.setAttribute('tabindex', 0);
Un usuario puede establecer una propiedad en una instancia de un elemento antes de que su prototipo se haya conectado a esta clase. El método _upgradeProperty()
buscará propiedades de instancia y las ejecutará a través de los set de clase adecuados. Consulta la sección propiedades diferidas para obtener más detalles.
this._upgradeProperty('checked');
this._upgradeProperty('disabled');
this.addEventListener('keyup', this._onKeyUp);
this.addEventListener('click', this._onClick);
}
_upgradeProperty(prop) {
if (this.hasOwnProperty(prop)) {
let value = this[prop];
delete this[prop];
this[prop] = value;
}
}
disconnectedCallback()
se activa cuando se quita el elemento del DOM. Es un buen lugar para realizar tareas de limpieza, como liberar referencias y quitar objetos de escucha de eventos.
disconnectedCallback() {
this.removeEventListener('keyup', this._onKeyUp);
this.removeEventListener('click', this._onClick);
}
Las propiedades y sus atributos correspondientes deben reflejarse entre sí. El set de propiedades para checked controla los valores verdaderos o falsos y los refleja en el estado del atributo. Consulta la sección evitar reingresiones para obtener más detalles.
set checked(value) {
const isChecked = Boolean(value);
if (isChecked)
this.setAttribute('checked', '');
else
this.removeAttribute('checked');
}
get checked() {
return this.hasAttribute('checked');
}
set disabled(value) {
const isDisabled = Boolean(value);
if (isDisabled)
this.setAttribute('disabled', '');
else
this.removeAttribute('disabled');
}
get disabled() {
return this.hasAttribute('disabled');
}
Se llama a attributeChangedCallback()
cuando se cambia alguno de los atributos del array observedAttributes. Es un buen lugar para controlar los efectos secundarios, como configurar atributos ARIA.
attributeChangedCallback(name, oldValue, newValue) {
const hasValue = newValue !== null;
switch (name) {
case 'checked':
this.setAttribute('aria-checked', hasValue);
break;
case 'disabled':
this.setAttribute('aria-disabled', hasValue);
El atributo tabindex
no proporciona una forma de quitar por completo la capacidad de enfoque de un elemento. Los elementos con tabindex=-1
se pueden enfocar con el mouse o llamando a focus()
. Para asegurarte de que un elemento esté inhabilitado y no se pueda enfocar, quita el atributo tabindex
.
if (hasValue) {
this.removeAttribute('tabindex');
Si el enfoque está actualmente en este elemento, para quitar el foco, llama al método HTMLElement.blur()
.
this.blur();
} else {
this.setAttribute('tabindex', '0');
}
break;
}
}
_onKeyUp(event) {
No controles los accesos directos de modificadores que suele usar la tecnología de accesibilidad.
if (event.altKey)
return;
switch (event.keyCode) {
case KEYCODE.SPACE:
event.preventDefault();
this._toggleChecked();
break;
Se ignora cualquier otra tecla presionada y se pasa al navegador.
default:
return;
}
}
_onClick(event) {
this._toggleChecked();
}
_toggleChecked()
llama al método set verificado e invierte su estado. Como _toggleChecked()
solo se produce por una acción del usuario, también se enviará un evento de cambio. Este evento se eleva para imitar el comportamiento nativo de <input type=checkbox>
.
_toggleChecked() {
if (this.disabled)
return;
this.checked = !this.checked;
this.dispatchEvent(new CustomEvent('change', {
detail: {
checked: this.checked,
},
bubbles: true,
}));
}
}
customElements.define('howto-checkbox', HowToCheckbox);
})();