CINXE.COM

Cómo crear widgets de formularios personalizados - Aprende desarrollo web | MDN

<!doctype html><html lang="en-US" prefix="og: https://ogp.me/ns#"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="icon" href="https://developer.mozilla.org/favicon-48x48.bc390275e955dacb2e65.png"/><link rel="apple-touch-icon" href="https://developer.mozilla.org/apple-touch-icon.528534bba673c38049c2.png"/><meta name="theme-color" content="#ffffff"/><link rel="manifest" href="https://developer.mozilla.org/manifest.f42880861b394dd4dc9b.json"/><link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="MDN Web Docs"/><title>Cómo crear widgets de formularios personalizados - Aprende desarrollo web | MDN</title><link rel="alternate" title="How to build custom form controls" href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls" hrefLang="en"/><link rel="alternate" title="Anleitung zur Erstellung benutzerdefinierter Formularsteuerungen" href="https://developer.mozilla.org/de/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls" hrefLang="de"/><link rel="alternate" title="Comment construire des widgets de formulaires personnalisés" href="https://developer.mozilla.org/fr/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls" hrefLang="fr"/><link rel="alternate" title="カスタムフォームコントロールの作成方法" href="https://developer.mozilla.org/ja/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls" hrefLang="ja"/><link rel="alternate" title="Как создавать пользовательские виджеты форм" href="https://developer.mozilla.org/ru/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls" hrefLang="ru"/><link rel="alternate" title="如何构建自定义表单控件" href="https://developer.mozilla.org/zh-CN/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls" hrefLang="zh"/><link rel="alternate" title="Cómo crear widgets de formularios personalizados" href="https://developer.mozilla.org/es/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls" hrefLang="es"/><link rel="preload" as="font" type="font/woff2" href="/static/media/Inter.var.c2fe3cb2b7c746f7966a.woff2" crossorigin=""/><link rel="alternate" type="application/rss+xml" title="MDN Blog RSS Feed" href="https://developer.mozilla.org/en-US/blog/rss.xml" hrefLang="en"/><meta name="description" content="page(Doc) not found /es/docs/Learn/HTML/Forms/Form_validation"/><meta property="og:url" content="https://developer.mozilla.org/es/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls"/><meta property="og:title" content="Cómo crear widgets de formularios personalizados - Aprende desarrollo web | MDN"/><meta property="og:type" content="website"/><meta property="og:locale" content="es"/><meta property="og:description" content="page(Doc) not found /es/docs/Learn/HTML/Forms/Form_validation"/><meta property="og:image" content="https://developer.mozilla.org/mdn-social-share.d893525a4fb5fb1f67a2.png"/><meta property="og:image:type" content="image/png"/><meta property="og:image:height" content="1080"/><meta property="og:image:width" content="1920"/><meta property="og:image:alt" content="The MDN Web Docs logo, featuring a blue accent color, displayed on a solid black background."/><meta property="og:site_name" content="MDN Web Docs"/><meta name="twitter:card" content="summary_large_image"/><meta name="twitter:creator" content="MozDevNet"/><link rel="canonical" href="https://developer.mozilla.org/es/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls"/><style media="print">.article-actions-container,.document-toc-container,.language-menu,.main-menu-toggle,.on-github,.page-footer,.place,.sidebar,.top-banner,.top-navigation-main,ul.prev-next{display:none!important}.main-page-content,.main-page-content pre{padding:2px}.main-page-content pre{border-left-width:2px}</style><script src="/static/js/gtag.js" defer=""></script><script defer="" src="/static/js/main.f565372a.js"></script><link href="/static/css/main.3d9e7a02.css" rel="stylesheet"/></head><body><script>if(document.body.addEventListener("load",(t=>{t.target.classList.contains("interactive")&&t.target.setAttribute("data-readystate","complete")}),{capture:!0}),window&&document.documentElement){const t={light:"#ffffff",dark:"#1b1b1b"};try{const e=window.localStorage.getItem("theme");e&&(document.documentElement.className=e,document.documentElement.style.backgroundColor=t[e]);const o=window.localStorage.getItem("nop");o&&(document.documentElement.dataset.nop=o)}catch(t){console.warn("Unable to read theme from localStorage",t)}}</script><div id="root"><ul id="nav-access" class="a11y-nav"><li><a id="skip-main" href="#content">Skip to main content</a></li><li><a id="skip-search" href="#top-nav-search-input">Skip to search</a></li><li><a id="skip-select-language" href="#languages-switcher-button">Skip to select language</a></li></ul><div class="page-wrapper category-api document-page"><div class="top-banner loading"><section class="place top container"></section></div><div class="sticky-header-container"><header class="top-navigation "><div class="container "><div class="top-navigation-wrap"><a href="/es/" class="logo" aria-label="MDN homepage"><svg id="mdn-docs-logo" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 694.9 104.4" style="enable-background:new 0 0 694.9 104.4" xml:space="preserve" role="img"><title>MDN Web Docs</title><path d="M40.3 0 11.7 92.1H0L28.5 0h11.8zm10.4 0v92.1H40.3V0h10.4zM91 0 62.5 92.1H50.8L79.3 0H91zm10.4 0v92.1H91V0h10.4z" class="logo-m"></path><path d="M627.9 95.6h67v8.8h-67v-8.8z" class="logo-_"></path><path d="M367 42h-4l-10.7 30.8h-5.5l-10.8-26h-.4l-10.5 26h-5.2L308.7 42h-3.8v-5.6H323V42h-6.5l6.8 20.4h.4l10.3-26h4.7l11.2 26h.5l5.7-20.3h-6.2v-5.6H367V42zm34.9 20c-.4 3.2-2 5.9-4.7 8.2-2.8 2.3-6.5 3.4-11.3 3.4-5.4 0-9.7-1.6-13.1-4.7-3.3-3.2-5-7.7-5-13.7 0-5.7 1.6-10.3 4.7-14s7.4-5.5 12.9-5.5c5.1 0 9.1 1.6 11.9 4.7s4.3 6.9 4.3 11.3c0 1.5-.2 3-.5 4.7h-25.6c.3 7.7 4 11.6 10.9 11.6 2.9 0 5.1-.7 6.5-2 1.5-1.4 2.5-3 3-4.9l6 .9zM394 51.3c.2-2.4-.4-4.7-1.8-6.9s-3.8-3.3-7-3.3c-3.1 0-5.3 1-6.9 3-1.5 2-2.5 4.4-2.8 7.2H394zm51 2.4c0 5-1.3 9.5-4 13.7s-6.9 6.2-12.7 6.2c-6 0-10.3-2.2-12.7-6.7-.1.4-.2 1.4-.4 2.9s-.3 2.5-.4 2.9h-7.3c.3-1.7.6-3.5.8-5.3.3-1.8.4-3.7.4-5.5V22.3h-6v-5.6H416v27c1.1-2.2 2.7-4.1 4.7-5.7 2-1.6 4.8-2.4 8.4-2.4 4.6 0 8.4 1.6 11.4 4.7 3 3.2 4.5 7.6 4.5 13.4zm-7.7.6c0-4.2-1-7.4-3-9.5-2-2.2-4.4-3.3-7.4-3.3-3.4 0-6 1.2-8 3.7-1.9 2.4-2.9 5-3 7.7V57c0 3 1 5.6 3 7.7s4.5 3.1 7.6 3.1c3.6 0 6.3-1.3 8.1-3.9 1.8-2.7 2.7-5.9 2.7-9.6zm69.2 18.5h-13.2v-7.2c-1.2 2.2-2.8 4.1-4.9 5.6-2.1 1.6-4.8 2.4-8.3 2.4-4.8 0-8.7-1.6-11.6-4.9-2.9-3.2-4.3-7.7-4.3-13.3 0-5 1.3-9.6 4-13.7 2.6-4.1 6.9-6.2 12.8-6.2 5.7 0 9.8 2.2 12.3 6.5V22.3h-8.6v-5.6h15.8v50.6h6v5.5zM493.2 56v-4.4c-.1-3-1.2-5.5-3.2-7.3s-4.4-2.8-7.2-2.8c-3.6 0-6.3 1.3-8.2 3.9-1.9 2.6-2.8 5.8-2.8 9.6 0 4.1 1 7.3 3 9.5s4.5 3.3 7.4 3.3c3.2 0 5.8-1.3 7.8-3.8 2.1-2.6 3.1-5.3 3.2-8zm53.1-1.4c0 5.6-1.8 10.2-5.3 13.7s-8.2 5.3-13.9 5.3-10.1-1.7-13.4-5.1c-3.3-3.4-5-7.9-5-13.5 0-5.3 1.6-9.9 4.7-13.7 3.2-3.8 7.9-5.7 14.2-5.7s11 1.9 14.1 5.7c3 3.7 4.6 8.1 4.6 13.3zm-7.7-.2c0-4-1-7.2-3-9.5s-4.8-3.5-8.2-3.5c-3.6 0-6.4 1.2-8.3 3.7s-2.9 5.6-2.9 9.5c0 3.7.9 6.8 2.8 9.4 1.9 2.6 4.6 3.9 8.3 3.9 3.6 0 6.4-1.3 8.4-3.8 1.9-2.6 2.9-5.8 2.9-9.7zm45 5.8c-.4 3.2-1.9 6.3-4.4 9.1-2.5 2.9-6.4 4.3-11.8 4.3-5.2 0-9.4-1.6-12.6-4.8-3.2-3.2-4.8-7.7-4.8-13.7 0-5.5 1.6-10.1 4.7-13.9 3.2-3.8 7.6-5.7 13.2-5.7 2.3 0 4.6.3 6.7.8 2.2.5 4.2 1.5 6.2 2.9l1.5 9.5-5.9.7-1.3-6.1c-2.1-1.2-4.5-1.8-7.2-1.8-3.5 0-6.1 1.2-7.7 3.7-1.7 2.5-2.5 5.7-2.5 9.6 0 4.1.9 7.3 2.7 9.5 1.8 2.3 4.4 3.4 7.8 3.4 5.2 0 8.2-2.9 9.2-8.8l6.2 1.3zm34.7 1.9c0 3.6-1.5 6.5-4.6 8.5s-7 3-11.7 3c-5.7 0-10.6-1.2-14.6-3.6l1.2-8.8 5.7.6-.2 4.7c1.1.5 2.3.9 3.6 1.1s2.6.3 3.9.3c2.4 0 4.5-.4 6.5-1.3 1.9-.9 2.9-2.2 2.9-4.1 0-1.8-.8-3.1-2.3-3.8s-3.5-1.3-5.8-1.7-4.6-.9-6.9-1.4c-2.3-.6-4.2-1.6-5.7-2.9-1.6-1.4-2.3-3.5-2.3-6.3 0-4.1 1.5-6.9 4.6-8.5s6.4-2.4 9.9-2.4c2.6 0 5 .3 7.2.9 2.2.6 4.3 1.4 6.1 2.4l.8 8.8-5.8.7-.8-5.7c-2.3-1-4.7-1.6-7.2-1.6-2.1 0-3.7.4-5.1 1.1-1.3.8-2 2-2 3.8 0 1.7.8 2.9 2.3 3.6 1.5.7 3.4 1.2 5.7 1.6 2.2.4 4.5.8 6.7 1.4 2.2.6 4.1 1.6 5.7 3 1.4 1.6 2.2 3.7 2.2 6.6zM197.6 73.2h-17.1v-5.5h3.8V51.9c0-3.7-.7-6.3-2.1-7.9-1.4-1.6-3.3-2.3-5.7-2.3-3.2 0-5.6 1.1-7.2 3.4s-2.4 4.6-2.5 6.9v15.6h6v5.5h-17.1v-5.5h3.8V51.9c0-3.8-.7-6.4-2.1-7.9-1.4-1.5-3.3-2.3-5.6-2.3-3.2 0-5.5 1.1-7.2 3.3-1.6 2.2-2.4 4.5-2.5 6.9v15.8h6.9v5.5h-20.2v-5.5h6V42.4h-6.1v-5.6h13.4v6.4c1.2-2.1 2.7-3.8 4.7-5.2 2-1.3 4.4-2 7.3-2s5.3.7 7.5 2.1c2.2 1.4 3.7 3.5 4.5 6.4 1.1-2.5 2.7-4.5 4.9-6.1s4.8-2.4 7.9-2.4c3.5 0 6.5 1.1 8.9 3.3s3.7 5.6 3.7 10.2v18.2h6.1v5.5zm42.5 0h-13.2V66c-1.2 2.2-2.8 4.1-4.9 5.6-2.1 1.6-4.8 2.4-8.3 2.4-4.8 0-8.7-1.6-11.6-4.9-2.9-3.2-4.3-7.7-4.3-13.3 0-5 1.3-9.6 4-13.7 2.6-4.1 6.9-6.2 12.8-6.2s9.8 2.2 12.3 6.5V22.7h-8.6v-5.6h15.8v50.6h6v5.5zm-13.3-16.8V52c-.1-3-1.2-5.5-3.2-7.3s-4.4-2.8-7.2-2.8c-3.6 0-6.3 1.3-8.2 3.9-1.9 2.6-2.8 5.8-2.8 9.6 0 4.1 1 7.3 3 9.5s4.5 3.3 7.4 3.3c3.2 0 5.8-1.3 7.8-3.8 2.1-2.6 3.1-5.3 3.2-8zm61.5 16.8H269v-5.5h6V51.9c0-3.7-.7-6.3-2.2-7.9-1.4-1.6-3.4-2.3-5.7-2.3-3.1 0-5.6 1-7.4 3s-2.8 4.4-2.9 7v15.9h6v5.5h-19.3v-5.5h6V42.4h-6.2v-5.6h13.6V43c2.6-4.6 6.8-6.9 12.7-6.9 3.6 0 6.7 1.1 9.2 3.3s3.7 5.6 3.7 10.2v18.2h6v5.4h-.2z" class="logo-text"></path></svg></a><button title="Open main menu" type="button" class="button action has-icon main-menu-toggle" aria-haspopup="menu" aria-label="Open main menu" aria-expanded="false"><span class="button-wrap"><span class="icon icon-menu "></span><span class="visually-hidden">Open main menu</span></span></button></div><div class="top-navigation-main"><nav class="main-nav" aria-label="Main menu"><ul class="main-menu nojs"><li class="top-level-entry-container "><button type="button" id="references-button" class="top-level-entry menu-toggle" aria-controls="references-menu" aria-expanded="false">References</button><a href="/es/docs/Web" class="top-level-entry">References</a><ul id="references-menu" class="submenu references hidden inline-submenu-lg" aria-labelledby="references-button"><li class="apis-link-container mobile-only "><a href="/es/docs/Web" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Overview / Web Technology</div><p class="submenu-item-description">Web technology reference for developers</p></div></a></li><li class="html-link-container "><a href="/es/docs/Web/HTML" class="submenu-item "><div class="submenu-icon html"></div><div class="submenu-content-container"><div class="submenu-item-heading">HTML</div><p class="submenu-item-description">Structure of content on the web</p></div></a></li><li class="css-link-container "><a href="/es/docs/Web/CSS" class="submenu-item "><div class="submenu-icon css"></div><div class="submenu-content-container"><div class="submenu-item-heading">CSS</div><p class="submenu-item-description">Code used to describe document style</p></div></a></li><li class="javascript-link-container "><a href="/es/docs/Web/JavaScript" class="submenu-item "><div class="submenu-icon javascript"></div><div class="submenu-content-container"><div class="submenu-item-heading">JavaScript</div><p class="submenu-item-description">General-purpose scripting language</p></div></a></li><li class="http-link-container "><a href="/es/docs/Web/HTTP" class="submenu-item "><div class="submenu-icon http"></div><div class="submenu-content-container"><div class="submenu-item-heading">HTTP</div><p class="submenu-item-description">Protocol for transmitting web resources</p></div></a></li><li class="apis-link-container "><a href="/es/docs/Web/API" class="submenu-item "><div class="submenu-icon apis"></div><div class="submenu-content-container"><div class="submenu-item-heading">Web APIs</div><p class="submenu-item-description">Interfaces for building web applications</p></div></a></li><li class="apis-link-container "><a href="/es/docs/Mozilla/Add-ons/WebExtensions" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Web Extensions</div><p class="submenu-item-description">Developing extensions for web browsers</p></div></a></li><li class=" "><a href="/es/docs/Web/Accessibility" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Accessibility</div><p class="submenu-item-description">Build web projects usable for all</p></div></a></li><li class="apis-link-container desktop-only "><a href="/es/docs/Web" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Web Technology</div><p class="submenu-item-description">Web technology reference for developers</p></div></a></li></ul></li><li class="top-level-entry-container active"><button type="button" id="learn-button" class="top-level-entry menu-toggle" aria-controls="learn-menu" aria-expanded="false">Learn</button><a href="/es/docs/Learn_web_development" class="top-level-entry">Learn</a><ul id="learn-menu" class="submenu learn hidden inline-submenu-lg" aria-labelledby="learn-button"><li class="apis-link-container mobile-only "><a href="/es/docs/Learn_web_development" class="submenu-item "><div class="submenu-icon learn"></div><div class="submenu-content-container"><div class="submenu-item-heading">Overview / MDN Learning Area</div><p class="submenu-item-description">Learn web development</p></div></a></li><li class="apis-link-container desktop-only "><a href="/es/docs/Learn_web_development" class="submenu-item "><div class="submenu-icon learn"></div><div class="submenu-content-container"><div class="submenu-item-heading">MDN Learning Area</div><p class="submenu-item-description">Learn web development</p></div></a></li><li class="html-link-container "><a href="/es/docs/Learn_web_development/Core/Structuring_content" class="submenu-item "><div class="submenu-icon html"></div><div class="submenu-content-container"><div class="submenu-item-heading">HTML</div><p class="submenu-item-description">Learn to structure web content with HTML</p></div></a></li><li class="css-link-container "><a href="/es/docs/Learn_web_development/Core/Styling_basics" class="submenu-item "><div class="submenu-icon css"></div><div class="submenu-content-container"><div class="submenu-item-heading">CSS</div><p class="submenu-item-description">Learn to style content using CSS</p></div></a></li><li class="javascript-link-container "><a href="/es/docs/Learn_web_development/Core/Scripting" class="submenu-item "><div class="submenu-icon javascript"></div><div class="submenu-content-container"><div class="submenu-item-heading">JavaScript</div><p class="submenu-item-description">Learn to run scripts in the browser</p></div></a></li><li class=" "><a href="/es/docs/Learn_web_development/Core/Accessibility" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Accessibility</div><p class="submenu-item-description">Learn to make the web accessible to all</p></div></a></li></ul></li><li class="top-level-entry-container "><button type="button" id="mdn-plus-button" class="top-level-entry menu-toggle" aria-controls="mdn-plus-menu" aria-expanded="false">Plus</button><a href="/es/plus" class="top-level-entry">Plus</a><ul id="mdn-plus-menu" class="submenu mdn-plus hidden inline-submenu-lg" aria-labelledby="mdn-plus-button"><li class=" "><a href="/es/plus" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Overview</div><p class="submenu-item-description">A customized MDN experience</p></div></a></li><li class=" "><a href="/es/plus/ai-help" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">AI Help</div><p class="submenu-item-description">Get real-time assistance and support</p></div></a></li><li class=" "><a href="/es/plus/updates" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Updates</div><p class="submenu-item-description">All browser compatibility updates at a glance</p></div></a></li><li class=" "><a href="/en-US/plus/docs/features/overview" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Documentation</div><p class="submenu-item-description">Learn how to use MDN Plus</p></div></a></li><li class=" "><a href="/en-US/plus/docs/faq" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">FAQ</div><p class="submenu-item-description">Frequently asked questions about MDN Plus</p></div></a></li></ul></li><li class="top-level-entry-container "><a class="top-level-entry menu-link" href="/en-US/curriculum/">Curriculum <sup class="new">New</sup></a></li><li class="top-level-entry-container "><a class="top-level-entry menu-link" href="/en-US/blog/">Blog</a></li><li class="top-level-entry-container "><button type="button" id="tools-button" class="top-level-entry menu-toggle" aria-controls="tools-menu" aria-expanded="false">Tools</button><ul id="tools-menu" class="submenu tools hidden inline-submenu-lg" aria-labelledby="tools-button"><li class=" "><a href="/es/play" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Playground</div><p class="submenu-item-description">Write, test and share your code</p></div></a></li><li class=" "><a href="/en-US/observatory" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">HTTP Observatory</div><p class="submenu-item-description">Scan a website for free</p></div></a></li><li class=" "><a href="/en-US/plus/ai-help" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">AI Help</div><p class="submenu-item-description">Get real-time assistance and support</p></div></a></li></ul></li></ul></nav><div class="header-search"><form action="/es/search" class="search-form search-widget" id="top-nav-search-form" role="search"><label id="top-nav-search-label" for="top-nav-search-input" class="visually-hidden">Search MDN</label><input aria-activedescendant="" aria-autocomplete="list" aria-controls="top-nav-search-menu" aria-expanded="false" aria-labelledby="top-nav-search-label" autoComplete="off" id="top-nav-search-input" role="combobox" type="search" class="search-input-field" name="q" placeholder="   " required="" value=""/><button type="button" class="button action has-icon clear-search-button"><span class="button-wrap"><span class="icon icon-cancel "></span><span class="visually-hidden">Clear search input</span></span></button><button type="submit" class="button action has-icon search-button"><span class="button-wrap"><span class="icon icon-search "></span><span class="visually-hidden">Search</span></span></button><div id="top-nav-search-menu" role="listbox" aria-labelledby="top-nav-search-label"></div></form></div><div class="theme-switcher-menu"><button type="button" class="button action has-icon theme-switcher-menu small" aria-haspopup="menu"><span class="button-wrap"><span class="icon icon-theme-os-default "></span>Theme</span></button></div><ul class="auth-container"><li><a href="/users/fxa/login/authenticate/?next=%2Fes%2Fdocs%2FLearn_web_development%2FExtensions%2FForms%2FHow_to_build_custom_form_controls" class="login-link" rel="nofollow">Log in</a></li><li><a href="/users/fxa/login/authenticate/?next=%2Fes%2Fdocs%2FLearn_web_development%2FExtensions%2FForms%2FHow_to_build_custom_form_controls" target="_self" rel="nofollow" class="button primary mdn-plus-subscribe-link"><span class="button-wrap">Sign up for free</span></a></li></ul></div></div></header><div class="article-actions-container"><div class="container"><button type="button" class="button action has-icon sidebar-button" aria-label="Expand sidebar" aria-expanded="false" aria-controls="sidebar-quicklinks"><span class="button-wrap"><span class="icon icon-sidebar "></span></span></button><nav class="breadcrumbs-container" aria-label="Breadcrumb"><ol typeof="BreadcrumbList" vocab="https://schema.org/" aria-label="breadcrumbs"><li property="itemListElement" typeof="ListItem"><a href="/es/docs/Learn_web_development" class="breadcrumb" property="item" typeof="WebPage"><span property="name">Aprende desarrollo web</span></a><meta property="position" content="1"/></li><li property="itemListElement" typeof="ListItem"><a href="/en-US/docs/Learn_web_development/Extensions" class="breadcrumb" property="item" typeof="WebPage"><span property="name">Extension modules</span></a><meta property="position" content="2"/></li><li property="itemListElement" typeof="ListItem"><a href="/es/docs/Learn_web_development/Extensions/Forms" class="breadcrumb" property="item" typeof="WebPage"><span property="name">Pilares de los formularios web</span></a><meta property="position" content="3"/></li><li property="itemListElement" typeof="ListItem"><a href="/es/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls" class="breadcrumb-current-page" property="item" typeof="WebPage"><span property="name">Cómo crear widgets de formularios personalizados</span></a><meta property="position" content="4"/></li></ol></nav><div class="article-actions"><button type="button" class="button action has-icon article-actions-toggle" aria-label="Article actions"><span class="button-wrap"><span class="icon icon-ellipses "></span><span class="article-actions-dialog-heading">Article Actions</span></span></button><ul class="article-actions-entries"><li class="article-actions-entry"><div class="languages-switcher-menu open-on-focus-within"><button id="languages-switcher-button" type="button" class="button action small has-icon languages-switcher-menu" aria-haspopup="menu"><span class="button-wrap"><span class="icon icon-language "></span>Español</span></button><div class="hidden"><ul class="submenu language-menu " aria-labelledby="language-menu-button"><li class=" "><form class="submenu-item locale-redirect-setting"><div class="group"><label class="switch"><input type="checkbox" name="locale-redirect"/><span class="slider"></span><span class="label">Remember language</span></label><a href="https://github.com/orgs/mdn/discussions/739" rel="external noopener noreferrer" target="_blank" title="Enable this setting to automatically switch to this language when it&#x27;s available. (Click to learn more.)"><span class="icon icon-question-mark "></span></a></div></form></li><li class=" "><a data-locale="en-US" href="/en-US/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls" class="button submenu-item"><span>English (US)</span></a></li><li class=" "><a data-locale="de" href="/de/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls" class="button submenu-item"><span>Deutsch</span><span title="Diese Übersetzung ist Teil eines Experiments."><span class="icon icon-experimental "></span></span></a></li><li class=" "><a data-locale="fr" href="/fr/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls" class="button submenu-item"><span>Français</span></a></li><li class=" "><a data-locale="ja" href="/ja/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls" class="button submenu-item"><span>日本語</span></a></li><li class=" "><a data-locale="ru" href="/ru/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls" class="button submenu-item"><span>Русский</span></a></li><li class=" "><a data-locale="zh-CN" href="/zh-CN/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls" class="button submenu-item"><span>中文 (简体)</span></a></li></ul></div></div></li></ul></div></div></div></div><div class="container"><div class="notecard localized-content-note"><p><a href="/es/docs/MDN/Community/Contributing/Translated_content#locales_activos">Esta página ha sido traducida del inglés por la comunidad. Aprende más y únete a la comunidad de MDN Web Docs.</a></p></div></div><div class="main-wrapper"><div class="sidebar-container"><aside id="sidebar-quicklinks" class="sidebar"><button type="button" class="button action backdrop" aria-label="Collapse sidebar"><span class="button-wrap"></span></button><nav aria-label="Related Topics" class="sidebar-inner"><header class="sidebar-actions"><section class="sidebar-filter-container"><div class="sidebar-filter "><label id="sidebar-filter-label" class="sidebar-filter-label" for="sidebar-filter-input"><span class="icon icon-filter"></span><span class="visually-hidden">Filter sidebar</span></label><input id="sidebar-filter-input" autoComplete="off" class="sidebar-filter-input-field false" type="text" placeholder="Filter" value=""/><button type="button" class="button action has-icon clear-sidebar-filter-button"><span class="button-wrap"><span class="icon icon-cancel "></span><span class="visually-hidden">Clear filter input</span></span></button></div></section></header><div class="sidebar-inner-nav"><div class="in-nav-toc"><div class="document-toc-container"><section class="document-toc"><header><h2 class="document-toc-heading">En este artículo</h2></header><ul class="document-toc-list"><li class="document-toc-item "><a class="document-toc-link" href="#diseño_estructura_y_semántica">Diseño, estructura, y semántica</a></li><li class="document-toc-item "><a class="document-toc-link" href="#bring_your_widget_to_life_with_javascript">Bring your widget to life with JavaScript</a></li><li class="document-toc-item "><a class="document-toc-link" href="#make_it_accessible">Make it accessible</a></li><li class="document-toc-item "><a class="document-toc-link" href="#conclusion">Conclusion</a></li></ul></section></div></div><div class="sidebar-body"><ol><li class="section"><a href="/en-US/docs/Learn_web_development/Getting_started" class="only-in-en-us">Getting started modules</a></li><li class="toggle"><details><summary><a href="/en-US/docs/Learn_web_development/Getting_started/Environment_setup" class="only-in-en-us">Environment setup</a></summary><ol><li><a href="/es/docs/Learn_web_development/Getting_started/Environment_setup/Installing_software">Instalación de software básico</a></li><li><a href="/es/docs/Learn_web_development/Getting_started/Environment_setup/Browsing_the_web">¿Cuál es la diferencia entre la página web, el sitio web, el servidor web y el motor de búsqueda?</a></li><li><a href="/en-US/docs/Learn_web_development/Getting_started/Environment_setup/Code_editors" class="only-in-en-us">Code editors</a></li><li><a href="/es/docs/Learn_web_development/Getting_started/Environment_setup/Dealing_with_files">Manejo de archivos</a></li><li><a href="/en-US/docs/Learn_web_development/Getting_started/Environment_setup/Command_line" class="only-in-en-us">Command line crash course</a></li></ol></details></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Getting_started/Your_first_website">Your first website</a></summary><ol><li><a href="/es/docs/Learn_web_development/Getting_started/Your_first_website/What_will_your_website_look_like">¿Cuál será la apariencia de tu sitio Web?</a></li><li><a href="/es/docs/Learn_web_development/Getting_started/Your_first_website/Creating_the_content">Conceptos básicos de HTML</a></li><li><a href="/es/docs/Learn_web_development/Getting_started/Your_first_website/Styling_the_content">CSS básico</a></li><li><a href="/es/docs/Learn_web_development/Getting_started/Your_first_website/Adding_interactivity">Fundamentos de JavaScript</a></li><li><a href="/es/docs/Learn_web_development/Getting_started/Your_first_website/Publishing_your_website">Publicar tu sitio web</a></li></ol></details></li><li class="toggle"><details><summary><a href="/en-US/docs/Learn_web_development/Getting_started/Web_standards" class="only-in-en-us">Web standards</a></summary><ol><li><a href="/es/docs/Learn_web_development/Getting_started/Web_standards/How_the_web_works">Cómo funciona la web</a></li><li><a href="/es/docs/Learn_web_development/Getting_started/Web_standards/The_web_standards_model">La web y los estándares web</a></li><li><a href="/en-US/docs/Learn_web_development/Getting_started/Web_standards/How_browsers_load_websites" class="only-in-en-us">How browsers load websites</a></li></ol></details></li><li class="toggle"><details><summary><a href="/en-US/docs/Learn_web_development/Getting_started/Soft_skills" class="only-in-en-us">Soft skills</a></summary><ol><li><a href="/en-US/docs/Learn_web_development/Getting_started/Soft_skills/Research_and_learning" class="only-in-en-us">Research and learning</a></li><li><a href="/en-US/docs/Learn_web_development/Getting_started/Soft_skills/Collaboration_and_teamwork" class="only-in-en-us">Collaboration and teamwork</a></li><li><a href="/en-US/docs/Learn_web_development/Getting_started/Soft_skills/Workflows_and_processes" class="only-in-en-us">Workflows and processes</a></li><li><a href="/en-US/docs/Learn_web_development/Getting_started/Soft_skills/Job_interviews" class="only-in-en-us">Succeeding in job interviews</a></li></ol></details></li><li class="section"><a href="/en-US/docs/Learn_web_development/Core" class="only-in-en-us">Core modules</a></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Core/Structuring_content">Structuring content with HTML</a></summary><ol><li><a href="/es/docs/Learn_web_development/Core/Structuring_content/Basic_HTML_syntax">Primeros pasos con HTML</a></li><li><a href="/es/docs/Learn_web_development/Core/Structuring_content/Webpage_metadata">¿Qué hay en la cabecera? Metadatos en HTML</a></li><li><a href="/es/docs/Learn_web_development/Core/Structuring_content/Headings_and_paragraphs">Fundamentos de texto en HTML</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Structuring_content/Emphasis_and_importance" class="only-in-en-us">Emphasis and importance</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Structuring_content/Lists" class="only-in-en-us">Lists</a></li><li><a href="/es/docs/Learn_web_development/Core/Structuring_content/Structuring_documents">Estructura web y documentación</a></li><li><a href="/es/docs/Learn_web_development/Core/Structuring_content/Advanced_text_features">Formateo de texto avanzado</a></li><li><a href="/es/docs/Learn_web_development/Core/Structuring_content/Creating_links">Crear hipervínculos</a></li><li><a href="/es/docs/Learn_web_development/Core/Structuring_content/Marking_up_a_letter">Marcando una Carta</a></li><li><a href="/es/docs/Learn_web_development/Core/Structuring_content/Structuring_a_page_of_content">Estructuración de una Página de contenido</a></li><li><a href="/es/docs/Learn_web_development/Core/Structuring_content/HTML_images">Imágenes en HTML</a></li><li><a href="/es/docs/Learn_web_development/Core/Structuring_content/HTML_video_and_audio">Contenido de audio y video</a></li><li><a href="/es/docs/Learn_web_development/Core/Structuring_content/Mozilla_splash_page">Página de bienvenida de Mozilla</a></li><li><a href="/es/docs/Learn_web_development/Core/Structuring_content/HTML_table_basics">Conceptos básicos de las tablas HTML</a></li><li><a href="/es/docs/Learn_web_development/Core/Structuring_content/Table_accessibility">Funciones avanzadas de las tablas HTML y accesibilidad</a></li><li><a href="/es/docs/Learn_web_development/Core/Structuring_content/Planet_data_table">Evaluación: Estructurando datos planetarios</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Structuring_content/HTML_forms" class="only-in-en-us">Forms and buttons in HTML</a></li><li><a href="/es/docs/Learn_web_development/Core/Structuring_content/Debugging_HTML">Depurar el HTML</a></li></ol></details></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Core/Styling_basics">CSS styling basics</a></summary><ol><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/What_is_CSS">Cómo funciona CSS</a></li><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/Getting_started">Empezar con CSS</a></li><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/Styling_a_bio_page">Usa tu nuevo conocimiento</a></li><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/Basic_selectors">Selectores CSS</a></li><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/Attribute_selectors">Selectores de atributo</a></li><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/Pseudo_classes_and_elements">Pseudoclases y pseudoelementos</a></li><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/Combinators">Combinadores</a></li><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/Box_model">El modelo de caja</a></li><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/Handling_conflicts">Cascada y herencia</a></li><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/Values_and_units">Valores y unidades CSS</a></li><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/Sizing">Dimensionar elementos en CSS</a></li><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/Backgrounds_and_borders">Fondos y bordes</a></li><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/Overflow">Contenido desbordado</a></li><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/Images_media_forms">Imágenes, medios y elementos de formulario</a></li><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/Tables">Estilizando tablas</a></li><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/Debugging_CSS">Depurar el CSS</a></li><li><a href="/es/docs/Learn_web_development/Core/Styling_basics/Fundamental_CSS_comprehension">Comprensión de los fundamentos de CSS</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Styling_basics/Fancy_letterheaded_paper" class="only-in-en-us">Challenge: Creating fancy letterheaded paper</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Styling_basics/Cool-looking_box" class="only-in-en-us">Challenge: A cool-looking box</a></li></ol></details></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Core/Text_styling">CSS text styling</a></summary><ol><li><a href="/es/docs/Learn_web_development/Core/Text_styling/Fundamentals">Fundamentos de texto y fuentes tipográficas</a></li><li><a href="/es/docs/Learn_web_development/Core/Text_styling/Styling_lists">Aplicación de estilo a listas</a></li><li><a href="/es/docs/Learn_web_development/Core/Text_styling/Styling_links">Dar estilo a los enlaces</a></li><li><a href="/es/docs/Learn_web_development/Core/Text_styling/Web_fonts">Fuentes web</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Text_styling/Typesetting_a_homepage" class="only-in-en-us">Challenge: Typesetting a community school homepage</a></li></ol></details></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Core/CSS_layout">CSS layout</a></summary><ol><li><a href="/es/docs/Learn_web_development/Core/CSS_layout/Introduction">Introducción al diseño en CSS</a></li><li><a href="/es/docs/Learn_web_development/Core/CSS_layout/Floats">Floats</a></li><li><a href="/en-US/docs/Learn_web_development/Core/CSS_layout/Positioning" class="only-in-en-us">Positioning</a></li><li><a href="/es/docs/Learn_web_development/Core/CSS_layout/Flexbox">Flexbox</a></li><li><a href="/es/docs/Learn_web_development/Core/CSS_layout/Grids">Cuadrículas</a></li><li><a href="/es/docs/Learn_web_development/Core/CSS_layout/Responsive_Design">Diseño receptivo</a></li><li><a href="/en-US/docs/Learn_web_development/Core/CSS_layout/Media_queries" class="only-in-en-us">Media query fundamentals</a></li><li><a href="/en-US/docs/Learn_web_development/Core/CSS_layout/Fundamental_Layout_Comprehension" class="only-in-en-us">Challenge: Fundamental layout comprehension</a></li></ol></details></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Core/Scripting">Dynamic scripting with JavaScript</a></summary><ol><li><a href="/es/docs/Learn_web_development/Core/Scripting/What_is_JavaScript">¿Qué es JavaScript?</a></li><li><a href="/es/docs/Learn_web_development/Core/Scripting/A_first_splash">Un primer acercamiento a JavaScript</a></li><li><a href="/es/docs/Learn_web_development/Core/Scripting/What_went_wrong">¿Qué ha salido mal? Corrigiendo JavaScript</a></li><li><a href="/es/docs/Learn_web_development/Core/Scripting/Variables">Almacenando la información que necesitas - Variables</a></li><li><a href="/es/docs/Learn_web_development/Core/Scripting/Math">Matemáticas básicas en JavaScript — números y operadores</a></li><li><a href="/es/docs/Learn_web_development/Core/Scripting/Strings">Manejar texto — cadenas en JavaScript</a></li><li><a href="/es/docs/Learn_web_development/Core/Scripting/Useful_string_methods">Métodos útiles con cadenas</a></li><li><a href="/es/docs/Learn_web_development/Core/Scripting/Arrays">Arreglos</a></li><li><a href="/es/docs/Learn_web_development/Core/Scripting/Silly_story_generator">Generador de historias absurdas</a></li><li><a href="/es/docs/Learn_web_development/Core/Scripting/Conditionals">Tomando decisiones en tu código — condicionales</a></li><li><a href="/es/docs/Learn_web_development/Core/Scripting/Loops">Código de bucle</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Scripting/Functions" class="only-in-en-us">Functions — reusable blocks of code</a></li><li><a href="/es/docs/Learn_web_development/Core/Scripting/Build_your_own_function">Construye tu propia función</a></li><li><a href="/es/docs/Learn_web_development/Core/Scripting/Return_values">Una función retorna valores</a></li><li><a href="/es/docs/Learn_web_development/Core/Scripting/Events">Introducción a los eventos</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Scripting/Event_bubbling" class="only-in-en-us">Event bubbling</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Scripting/Image_gallery" class="only-in-en-us">Challenge: Image gallery</a></li><li><a href="/es/docs/Learn_web_development/Core/Scripting/Object_basics">Conceptos básicos de los objetos JavaScript</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Scripting/DOM_scripting" class="only-in-en-us">DOM scripting introduction</a></li><li><a href="/es/docs/Learn_web_development/Core/Scripting/Network_requests">AJAX</a></li><li><a href="/es/docs/Learn_web_development/Core/Scripting/JSON">Trabajando con JSON</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Scripting/Debugging_JavaScript" class="only-in-en-us">Debugging JavaScript and handling errors</a></li></ol></details></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Core/Frameworks_libraries">JavaScript frameworks and libraries</a></summary><ol><li><a href="/en-US/docs/Learn_web_development/Core/Frameworks_libraries/Introduction" class="only-in-en-us">Introduction to client-side frameworks</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Frameworks_libraries/Main_features" class="only-in-en-us">Framework main features</a></li><li><a href="/es/docs/Learn_web_development/Core/Frameworks_libraries/React_getting_started">Primeros pasos en React</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Frameworks_libraries/React_todo_list_beginning" class="only-in-en-us">Beginning our React todo list</a></li><li><a href="/es/docs/Learn_web_development/Core/Frameworks_libraries/React_components">Creando componentes en nuestra app de React</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Frameworks_libraries/React_interactivity_events_state" class="only-in-en-us">React interactivity: Events and state</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Frameworks_libraries/React_interactivity_filtering_conditional_rendering" class="only-in-en-us">React interactivity: Editing, filtering, conditional rendering</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Frameworks_libraries/React_accessibility" class="only-in-en-us">Accessibility in React</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Frameworks_libraries/React_resources" class="only-in-en-us">React resources</a></li></ol></details></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Core/Accessibility">Accessibility</a></summary><ol><li><a href="/es/docs/Learn_web_development/Core/Accessibility/What_is_accessibility">¿Qué es la accesibilidad?</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Accessibility/Tooling" class="only-in-en-us">Accessibility tooling and assistive technology</a></li><li><a href="/es/docs/Learn_web_development/Core/Accessibility/HTML">HTML: Una buena base para la accesibilidad</a></li><li><a href="/es/docs/Learn_web_development/Core/Accessibility/CSS_and_JavaScript">Buenas prácticas de accesibilidad CSS y JavaScript</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Accessibility/WAI-ARIA_basics" class="only-in-en-us">WAI-ARIA basics</a></li><li><a href="/en-US/docs/Learn_web_development/Core/Accessibility/Multimedia" class="only-in-en-us">Accessible multimedia</a></li><li><a href="/es/docs/Learn_web_development/Core/Accessibility/Mobile">Mobile accessibility</a></li><li><a href="/es/docs/Learn_web_development/Core/Accessibility/Accessibility_troubleshooting">Evaluación: Solución de problemas de accesibilidad</a></li></ol></details></li><li><a href="/en-US/docs/Learn_web_development/Core/Design_for_developers" class="only-in-en-us">Design for developers</a></li><li><a href="/es/docs/Learn_web_development/Core/Version_control">Version control</a></li><li class="section"><a href="/en-US/docs/Learn_web_development/Extensions" class="only-in-en-us">Extension modules</a></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects">Advanced JavaScript objects</a></summary><ol><li><a href="/es/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects/Object_prototypes">Prototipos de objetos</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects/Object-oriented_programming" class="only-in-en-us">Object-oriented programming</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects/Classes_in_JavaScript">Clases en JavaScript</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects/Object_building_practice">Ejercicio práctico de construcción de objetos</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects/Adding_bouncing_balls_features">Añadiendo características a nuestra demo de bouncing balls</a></li></ol></details></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Extensions/Client-side_APIs">Client-side web APIs</a></summary><ol><li><a href="/es/docs/Learn_web_development/Extensions/Client-side_APIs/Introduction">Introducción a las APIs web</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Client-side_APIs/Video_and_audio_APIs" class="only-in-en-us">Video and Audio APIs</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Client-side_APIs/Drawing_graphics" class="only-in-en-us">Drawing graphics</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Client-side_APIs/Client-side_storage">Almacenamiento del lado cliente</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Client-side_APIs/Third_party_APIs" class="only-in-en-us">Third-party APIs</a></li></ol></details></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Extensions/Async_JS">Asynchronous JavaScript</a></summary><ol><li><a href="/es/docs/Learn_web_development/Extensions/Async_JS/Introducing">Introducción a JavaScript asíncrono</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Async_JS/Promises" class="only-in-en-us">How to use promises</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Async_JS/Implementing_a_promise-based_API" class="only-in-en-us">How to implement a promise-based API</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Async_JS/Introducing_workers" class="only-in-en-us">Introducing workers</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Async_JS/Sequencing_animations" class="only-in-en-us">Challenge: Sequencing animations</a></li></ol></details></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Extensions/Forms">Web forms</a></summary><ol><li><a href="/es/docs/Learn_web_development/Extensions/Forms/Your_first_form">Mi primer formulario HTML</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Forms/How_to_structure_a_web_form">Cómo estructurar un formulario HTML</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Forms/Basic_native_form_controls">Controles de formulario originales</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Forms/HTML5_input_types">Tipos de input de HTML5</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Forms/Other_form_controls" class="only-in-en-us">Other form controls</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Forms/Styling_web_forms">Estilizando formularios HTML</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Forms/Advanced_form_styling" class="only-in-en-us">Advanced form styling</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Forms/UI_pseudo-classes" class="only-in-en-us">UI pseudo-classes</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Forms/Form_validation">Validación de formularios de datos</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Forms/Sending_and_retrieving_form_data">Sending form data</a></li></ol></details></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Extensions/Client-side_tools">Understanding client-side tools</a></summary><ol><li><a href="/en-US/docs/Learn_web_development/Extensions/Client-side_tools/Overview" class="only-in-en-us">Client-side tooling overview</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Client-side_tools/Package_management" class="only-in-en-us">Package management basics</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Client-side_tools/Introducing_complete_toolchain" class="only-in-en-us">Introducing a complete toolchain</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Client-side_tools/Deployment" class="only-in-en-us">Deploying our app</a></li></ol></details></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Extensions/Server-side">Server-side websites</a></summary><ol><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Extensions/Server-side/First_steps">Server-side first steps</a></summary><ol><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/First_steps/Introduction">Introducción al lado servidor</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/First_steps/Client-Server_overview">Visión General Cliente-Servidor</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/First_steps/Web_frameworks">Frameworks Web de lado servidor</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/First_steps/Website_security">Seguridad de Sitios Web</a></li></ol></details></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Extensions/Server-side/Django">Django web framework (Python)</a></summary><ol><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Django/Introduction">Introducción a Django</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Django/development_environment">Puesta en marcha de un entorno de desarrollo Django</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Django/Tutorial_local_library_website">Tutorial Django: El Sitio Web de La Biblioteca Local</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Django/skeleton_website">Tutorial Django Parte 2: Creación del esqueleto del sitio web</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Django/Models">Tutorial Django Parte 3: Uso de modelos</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Django/Admin_site">Tutorial Django Parte 4: Sitio de Administración de Django</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Django/Home_page">Tutorial de Django Parte 5: Creación de tu página de inicio</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Django/Generic_views">Tutorial de Django Parte 6: Listas genéricas y vistas de detalle</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Django/Sessions">Tutorial de Django Parte 7: Framework de sesiones</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Django/Authentication">Tutorial de Django Parte 8: Autenticación y permisos de Usuario</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Django/Forms">Tutorial de Django Parte 9: Trabajo con formularios</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Django/Testing">Tutorial de Django Parte 10: Probando una aplicación web Django</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Django/Deployment">Tutorial de Django Parte 11: Desplegando Django a producción</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Server-side/Django/web_application_security" class="only-in-en-us">Django web application security</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Server-side/Django/django_assessment_blog" class="only-in-en-us">Assessment: DIY Django mini blog</a></li></ol></details></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs">Express web framework (Node.js)</a></summary><ol><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Introduction">Introducción a Express/Node</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/development_environment">Setting up a Node development environment</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: El sitio web de la Biblioteca Local</a></li><li><a href="/es/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/mongoose" class="only-in-en-us">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/routes" class="only-in-en-us">Express Tutorial Part 4: Routes and controllers</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Displaying_data" class="only-in-en-us">Express Tutorial Part 5: Displaying library data</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/forms" class="only-in-en-us">Express Tutorial Part 6: Working with forms</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/deployment" class="only-in-en-us">Express Tutorial Part 7: Deploying to production</a></li></ol></details></li></ol></details></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Extensions/Performance">Web performance</a></summary><ol><li><a href="/en-US/docs/Learn_web_development/Extensions/Performance/why_web_performance" class="only-in-en-us">The "why" of web performance</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Performance/What_is_web_performance" class="only-in-en-us">What is web performance?</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Performance/Perceived_performance" class="only-in-en-us">Perceived performance</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Performance/Measuring_performance" class="only-in-en-us">Measuring performance</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Performance/Multimedia" class="only-in-en-us">Multimedia: Images</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Performance/video" class="only-in-en-us">Multimedia: video</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Performance/JavaScript" class="only-in-en-us">JavaScript performance optimization</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Performance/HTML" class="only-in-en-us">HTML performance optimization</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Performance/CSS" class="only-in-en-us">CSS performance optimization</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Performance/business_case_for_performance" class="only-in-en-us">The business case for web performance</a></li></ol></details></li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Extensions/Testing">Testing</a></summary><ol><li><a href="/en-US/docs/Learn_web_development/Extensions/Testing/Introduction" class="only-in-en-us">Introduction to cross-browser testing</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Testing/Testing_strategies" class="only-in-en-us">Strategies for carrying out testing</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Testing/HTML_and_CSS" class="only-in-en-us">Handling common HTML and CSS problems</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Testing/Feature_detection" class="only-in-en-us">Implementing feature detection</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Testing/Automated_testing" class="only-in-en-us">Introduction to automated testing</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Testing/Your_own_automation_environment" class="only-in-en-us">Setting up your own test automation environment</a></li></ol></details></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Transform_animate" class="only-in-en-us">Transform and animate CSS</a></li><li><a href="/en-US/docs/Learn_web_development/Extensions/Security_privacy" class="only-in-en-us">Security and privacy</a></li><li class="section">Further resources</li><li class="toggle"><details><summary><a href="/es/docs/Learn_web_development/Howto">How to solve common problems</a></summary><ol><li><a href="/es/docs/Learn_web_development/Howto/Solve_HTML_problems">Solución de problemas comunes de HTML</a></li><li><a href="/es/docs/Learn_web_development/Howto/Solve_CSS_problems">Usa CSS para resolver problemas comunes</a></li><li><a href="/es/docs/Learn_web_development/Howto/Solve_JavaScript_problems">Resuelva problemas comunes en su código JavaScript</a></li><li><a href="/en-US/docs/Learn_web_development/Howto/Web_mechanics" class="only-in-en-us">Web mechanics</a></li><li><a href="/en-US/docs/Learn_web_development/Howto/Tools_and_setup" class="only-in-en-us">Tools and setup</a></li><li><a href="/en-US/docs/Learn_web_development/Howto/Design_and_accessibility" class="only-in-en-us">Design and accessibility</a></li></ol></details></li><li><a href="/en-US/docs/Learn_web_development/About" class="only-in-en-us">About</a></li><li><a href="/en-US/docs/Learn_web_development/Educators" class="only-in-en-us">Resources for educators</a></li><li><a href="/en-US/docs/Learn_web_development/Changelog" class="only-in-en-us">Changelog</a></li></ol></div></div><section class="place side"></section></nav></aside><div class="toc-container"><aside class="toc"><nav><div class="document-toc-container"><section class="document-toc"><header><h2 class="document-toc-heading">En este artículo</h2></header><ul class="document-toc-list"><li class="document-toc-item "><a class="document-toc-link" href="#diseño_estructura_y_semántica">Diseño, estructura, y semántica</a></li><li class="document-toc-item "><a class="document-toc-link" href="#bring_your_widget_to_life_with_javascript">Bring your widget to life with JavaScript</a></li><li class="document-toc-item "><a class="document-toc-link" href="#make_it_accessible">Make it accessible</a></li><li class="document-toc-item "><a class="document-toc-link" href="#conclusion">Conclusion</a></li></ul></section></div></nav></aside><section class="place side"></section></div></div><main id="content" class="main-content "><article class="main-page-content" lang="es"><header><h1>Cómo crear widgets de formularios personalizados</h1></header><div class="section-content"><p>page(Doc) not found /es/docs/Learn/HTML/Forms/Form_validation</p> <p>Hay muchos casos donde los <a href="/es/docs/Learn_web_development/Extensions/Forms/Basic_native_form_controls">widgets de formularios HTML disponibles</a> simplemente no son suficientes. si desea <a href="/en-US/docs/Learn_web_development/Extensions/Forms/Advanced_form_styling" class="only-in-en-us">establecer un estilo avanzado</a> en algunos widgets como el elemento <a href="/es/docs/Web/HTML/Element/select"><code>&lt;select&gt;</code></a> o si desea proporcionar comportamientos personalizados, no tiene más opción que crear sus propios widgets.</p> <p>En este aartículo, veremos cómo construir dicho widget. Para ello, trabajaremos con un ejemplo: Reconstruir el elemento <a href="/es/docs/Web/HTML/Element/select"><code>&lt;select&gt;</code></a>.</p> <div class="notecard note"> <p><strong>Nota:</strong> Nos enfocaremos en construir los widgets, no en cómo hacer que el código sea genérico y reutilizable; eso implicaría algún código JavaScript no trivial y manipulación del DOM en un contexto desconocido, y eso está fuera del alcance de este artículo.</p> </div></div><section aria-labelledby="diseño_estructura_y_semántica"><h2 id="diseño_estructura_y_semántica"><a href="#diseño_estructura_y_semántica">Diseño, estructura, y semántica</a></h2><div class="section-content"><p>Antes de crear un widget personalizado, debería iniciar por averiguar exactamente qué es lo que desea. Esto le ahorarrá tiempo considerable. En particular, es importante definir claramente todos los estados de su widget. Para hacer esto, es bueno comenzar con un widget existente, cuyos estados y comportamientos son bien conocidos, por lo que simplemente puede imitarlos tanto como sea posible.</p> <p>En nuestro ejemplo, reconstruiremos el elemento <a href="/es/docs/Web/HTML/Element/select"><code>&lt;select&gt;</code></a>. Este es el resultado que queremos lograr:</p> <p><img src="/files/4481/custom-select.png" alt="The three states of a select box" loading="lazy"></p> <p>Esta captura de pantall muestra los tres estados principales de nuestro widget: el estado normal (a la izquiera); el estado activo (en el centro) y el estado abierto (a la derecha).</p> <p>En términos de comportamiento, queremos que nuestro widget sea utilizable tanto con un ratón como con un teclado, al igual que cualquier widget nativo. Comencemos por definir cómo el widget llega a cada estado:</p> <p>El widget está en su estado normal cuando:</p> <ul> <li>La página carga</li> <li>El widget estaba activo y el usuario hace clic en cualquier lugar fuera del widget</li> <li>El widget estaba activo y el usuario mueve el foco a otro widget usando el teclado</li> </ul> <div class="notecard note"> <p><strong>Nota:</strong> Mover el foco al rededor de la página generalmente se hace presionando la tecla de tabulación, pero este no es el estándar en todas partes. Por ejemplo, el ciclo a través de enlaces en una página se realiza en Safari de forma predeterminada usando la combinación <a href="https://www.456bereastreet.com/archive/200906/enabling_keyboard_navigation_in_mac_os_x_web_browsers/" class="external" target="_blank">combinación Opction+Tab</a>.</p> </div> <p>El widget está en su estado activo cuando:</p> <ul> <li>El usuario hace clic en él</li> <li>El usuario presiona la tecla tab y obtiene foco</li> <li>El widget estaba en su estado abierto y el usuario hace clic en el widget.</li> </ul> <p>El widget está en su estado abierto cuando:</p> <ul> <li>El widget está en cualquier otro estado diferente a abierto y el usuario hace clic en él.</li> </ul> <p>Una vez que sabemos cómo cambiar los estados, es importante definir cómo cambiar el valor del widget:</p> <p>El valor cambia cuando:</p> <ul> <li>El usuario hace clic en una opción cuando el widget está en estado abierto</li> <li>El usuario pulsa las teclas de flecha hacia arriba o hacia abajocuando el widget está en estado activo</li> </ul> <p>Finalmente, definamos cómo se comportarán las opciones del widget:</p> <ul> <li>Cuando se abre el widget, se resalta la opción seleccionada</li> <li>Cuando el ratón está sobre una opción, la opción se resalta y la opción resaltada anteriormente vuelve a su estado normal</li> </ul> <p>Para los fines de nuestro ejemplo, nos detendremos con eso; sin embargo, si eres un lector cuidadoso, notarás que faltan algunos comportamientos. Por ejemplo, ¿qué crees que sucederá si el usuario pulsa la tecla de tabulación mientras el widget está en estado abierto? La respuesta es ... nada. OK, el comportamiento correcto parece obvio, pero el hecho es que, como no está definido en nuestras especificaciones, es muy fácil pasar por alto este comportamiento. Esto es especialmente cierto en un entorno de equipo cuando las personas que diseñan el comportamiento del widget son diferentes de las que lo implementan.</p> <p>Otro ejemplo divertido: ¿qué pasará si el usuario pulsa las teclas de flecha hacia arriba o hacia abajo mientras el widget está en estado abierto? Este es un poco más complicado. Si considera que el estado activo y el estado abierto son completamente diferentes, la respuesta es nuevamente "no pasará nada" porque no definimos ninguna interacción de teclado para el estado abierto. Por otro lado, si considera que el estado activo y el estado abierto se superponen un poco, el valor puede cambiar pero la opción definitivamente no se resaltará en consecuencia, una vez más porque no definimos ninguna interacción del teclado sobre las opciones cuando el widget es en su estado abierto (solo hemos definido lo que debería suceder cuando se abre el widget, pero nada después de eso).</p> <p>En nuestro ejemplo, las especificaciones faltantes son obvias, así que las manejaremos, pero puede ser un problema real en widgets nuevos y exóticos, para los cuales nadie tiene la menor idea de cuál es el comportamiento correcto. Por lo tanto, siempre es bueno pasar tiempo en esta etapa de diseño, porque si defines un comportamiento deficiente u olvidas definir uno, será muy difícil redefinirlo una vez que los usuarios se hayan acostumbrado. Si tiene dudas, solicite las opiniones de los demás y, si tiene el presupuesto para ello, no dude en realizar las pruebas de usuario. Este proceso se llama Diseño UX. Si desea obtener más información sobre este tema, debe consultar los siguientes recursos útiles:</p> <ul> <li><a href="https://www.uxmatters.com/" class="external" target="_blank">UXMatters.com</a></li> <li><a href="https://uxdesign.com/" class="external" target="_blank">UXDesign.com</a></li> <li><a href="https://uxdesign.smashingmagazine.com/" class="external" target="_blank">The UX Design section of SmashingMagazine</a></li> </ul> <div class="notecard note"> <p><strong>Nota:</strong> Ademas, en la mayoría de los sistemas hay una forma de abrir el elemento <a href="/es/docs/Web/HTML/Element/select"><code>&lt;select&gt;</code></a> para ver todas las opciones disponibles (esto es lo mismo que hacer clic en el elemento <a href="/es/docs/Web/HTML/Element/select"><code>&lt;select&gt;</code></a> con un ratón). Esto se logra con Alt+Flecha abajo en Windows y no fué implementado en nuestro ejemplo —pero sería facil hacerlo, ya que el mecanismo ya se implementó para el evento <code>clic</code>.</p> </div></div></section><section aria-labelledby="definiendo_la_estructura_y_semántica_html"><h3 id="definiendo_la_estructura_y_semántica_html"><a href="#definiendo_la_estructura_y_semántica_html">Definiendo la estructura y semántica HTML</a></h3><div class="section-content"><p>Ahora que se ha decidido la funcionalidad básica del widget, es hora de comenzar a construir nuestro widget. El primer paso es definir su estructura HTML y darle una semántica básica. Esto es lo que necesitamos para reconstruir un elemento <a href="/es/docs/Web/HTML/Element/select"><code>&lt;select&gt;</code></a>:</p> <div class="code-example"><div class="example-header"><span class="language-name">html</span></div><pre class="brush: html notranslate"><code>&lt;!-- Este es nuestro contenedor principal para nuestro widget. El atributo tabindex es lo que permite al usuario enforcar el widget. Veremos más adelante que es mejor configurarlo a través de JavaScript. --&gt; &lt;div class="select" tabindex="0"&gt; &lt;!-- Este contenedor será usado para mostrar el valor actual del widget --&gt; &lt;span class="value"&gt;Cherry&lt;/span&gt; &lt;!-- Este contenedor contedrá todas las opciones disponibles para nuestro widget. Como es una lista, tiene sentido usar el elemento ul. --&gt; &lt;ul class="optList"&gt; &lt;!-- Cada opción solo contiene el valor que se mostrará, veremos más tarde cómo manejar el valor real que será enviado con el formulario de datos --&gt; &lt;li class="option"&gt;Cherry&lt;/li&gt; &lt;li class="option"&gt;Lemon&lt;/li&gt; &lt;li class="option"&gt;Banana&lt;/li&gt; &lt;li class="option"&gt;Strawberry&lt;/li&gt; &lt;li class="option"&gt;Apple&lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; </code></pre></div> <p>Tenga en cuanta el uso de nombres de clases; estos identifican cada parte relevante independientemente de los elementos HTML subyacentes reales utilizados. Esto es importante para garantizar que no vinculamos nuestro CSS y JavaScript a una estructura HTML sólida, de modo que podamos realizar cambios despues en la implementación sin romper el código que usa el widget. Pro ejemplo, si desea implementar el equivalente del elemento <a href="/en-US/docs/Web/HTML/Element/optgroup" class="only-in-en-us"><code>&lt;optgroup&gt;</code></a>.</p></div></section><section aria-labelledby="creating_the_look_and_feel_using_css"><h3 id="creating_the_look_and_feel_using_css"><a href="#creating_the_look_and_feel_using_css">Creating the look and feel using CSS</a></h3><div class="section-content"><p>Now that we have a structure, we can start designing our widget. The whole point of building this custom widget is to be able to style this widget exactly as we want. To that end, we will split our CSS work into two parts: the first part will be the CSS rules absolutely necessary to have our widget behave like a <a href="/es/docs/Web/HTML/Element/select"><code>&lt;select&gt;</code></a> element, and the second part will consist of the fancy styles used to make it look the way we want.</p> <h4 id="required_styles">Required styles</h4> <p>The required styles are those necessary to handle the three states of our widget.</p> <div class="code-example"><div class="example-header"><span class="language-name">css</span></div><pre class="brush: css notranslate"><code>.select { /* This will create a positioning context for the list of options */ position: relative; /* This will make our widget become part of the text flow and sizable at the same time */ display: inline-block; } </code></pre></div> <p>We need an extra class <code>active</code> to define the look and feel of our widget when it is in its active state. Because our widget is focusable, we double this custom style with the <a href="/es/docs/Web/CSS/:focus"><code>:focus</code></a> pseudo-class in order to be sure they will behave the same.</p> <div class="code-example"><div class="example-header"><span class="language-name">css</span></div><pre class="brush: css notranslate"><code>.select.active, .select:focus { outline: none; /* This box-shadow property is not exactly required, however it's so important to be sure the active state is visible that we use it as a default value, feel free to override it. */ box-shadow: 0 0 3px 1px #227755; } </code></pre></div> <p>Now, let's handle the list of options:</p> <div class="code-example"><div class="example-header"><span class="language-name">css</span></div><pre class="brush: css notranslate"><code>/* The .select selector here is syntactic sugar to be sure the classes we define are the ones inside our widget. */ .select .optList { /* This will make sure our list of options will be displayed below the value and out of the HTML flow */ position: absolute; top: 100%; left: 0; } </code></pre></div> <p>We need an extra class to handle when the list of options is hidden. This is necessary in order to manage the differences between the active state and the open state that do not exactly match.</p> <div class="code-example"><div class="example-header"><span class="language-name">css</span></div><pre class="brush: css notranslate"><code>.select .optList.hidden { /* This is a simple way to hide the list in an accessible way, we will talk more about accessibility in the end */ max-height: 0; visibility: hidden; } </code></pre></div> <h4 id="beautification">Beautification</h4> <p>So now that we have the basic functionality in place, the fun can start. The following is just an example of what is possible, and will match the screenshot at the beginning of this article. However, you should feel free to experiment and see what you can come up with.</p> <div class="code-example"><div class="example-header"><span class="language-name">css</span></div><pre class="brush: css notranslate"><code>.select { /* All sizes will be expressed with the em value for accessibility reasons (to make sure the widget remains resizable if the user uses the browser's zoom in a text-only mode). The computations are made assuming 1em == 16px which is the default value in most browsers. If you are lost with px to em conversion, try https://riddle.pl/emcalc/ */ font-size: 0.625em; /* this (10px) is the new font size context for em value in this context */ font-family: Verdana, Arial, sans-serif; -moz-box-sizing: border-box; box-sizing: border-box; /* We need extra room for the down arrow we will add */ padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */ width: 10em; /* 100px */ border: 0.2em solid #000; /* 2px */ border-radius: 0.4em; /* 4px */ box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */ /* The first declaration is for browsers that do not support linear gradients. The second declaration is because WebKit based browsers haven't unprefixed it yet. If you want to support legacy browsers, try https://www.colorzilla.com/gradient-editor/ */ background: #f0f0f0; background: -webkit-linear-gradient(90deg, #e3e3e3, #fcfcfc 50%, #f0f0f0); background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0); } .select .value { /* Because the value can be wider than our widget, we have to make sure it will not change the widget's width */ display: inline-block; width: 100%; overflow: hidden; vertical-align: top; /* And if the content overflows, it's better to have a nice ellipsis. */ white-space: nowrap; text-overflow: ellipsis; } </code></pre></div> <p>We don't need an extra element to design the down arrow; instead, we're using the <a href="/es/docs/Web/CSS/::after"><code>::after</code></a> pseudo-element. However, it could also be implemented using a simple background image on the <code>select</code> class.</p> <div class="code-example"><div class="example-header"><span class="language-name">css</span></div><pre class="brush: css notranslate"><code>.select:after { content: "▼"; /* We use the unicode caracter U+25BC; see https://www.utf8-chartable.de */ position: absolute; z-index: 1; /* This will be important to keep the arrow from overlapping the list of options */ top: 0; right: 0; -moz-box-sizing: border-box; box-sizing: border-box; height: 100%; width: 2em; /* 20px */ padding-top: 0.1em; /* 1px */ border-left: 0.2em solid #000; /* 2px */ border-radius: 0 0.1em 0.1em 0; /* 0 1px 1px 0 */ background-color: #000; color: #fff; text-align: center; } </code></pre></div> <p>Next, let's style the list of options:</p> <div class="code-example"><div class="example-header"><span class="language-name">css</span></div><pre class="brush: css notranslate"><code>.select .optList { z-index: 2; /* We explicitly said the list of options will always overlap the down arrow */ /* this will reset the default style of the ul element */ list-style: none; margin: 0; padding: 0; -moz-box-sizing: border-box; box-sizing: border-box; /* This will ensure that even if the values are smaller than the widget, the list of options will be as large as the widget itself */ min-width: 100%; /* In case the list is too long, its content will overflow vertically (which will add a vertical scrollbar automatically) but never horizontally (because we haven't set a width, the list will adjust its width automatically. If it can't, the content will be truncated) */ max-height: 10em; /* 100px */ overflow-y: auto; overflow-x: hidden; border: 0.2em solid #000; /* 2px */ border-top-width: 0.1em; /* 1px */ border-radius: 0 0 0.4em 0.4em; /* 0 0 4px 4px */ box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4); /* 0 2px 4px */ background: #f0f0f0; } </code></pre></div> <p>For the options, we need to add a <code>highlight</code> class to be able to identify the value the user will pick (or has picked).</p> <div class="code-example"><div class="example-header"><span class="language-name">css</span></div><pre class="brush: css notranslate"><code>.select .option { padding: 0.2em 0.3em; /* 2px 3px */ } .select .highlight { background: #000; color: #ffffff; } </code></pre></div> <p>So here's the result with our three states:</p> <h4 id="basic_state">Basic state</h4> <div class="code-example"><pre class="brush: html hidden notranslate"><code>&lt;div class="select"&gt; &lt;span class="value"&gt;Cherry&lt;/span&gt; &lt;ul class="optList hidden"&gt; &lt;li class="option"&gt;Cherry&lt;/li&gt; &lt;li class="option"&gt;Lemon&lt;/li&gt; &lt;li class="option"&gt;Banana&lt;/li&gt; &lt;li class="option"&gt;Strawberry&lt;/li&gt; &lt;li class="option"&gt;Apple&lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; </code></pre></div> <div class="code-example"><pre class="brush: css hidden notranslate"><code>.select { position: relative; display: inline-block; } .select.active, .select:focus { box-shadow: 0 0 3px 1px #227755; outline: none; } .select .optList { position: absolute; top: 100%; left: 0; } .select .optList.hidden { max-height: 0; visibility: hidden; } .select { font-size: 0.625em; /* 10px */ font-family: Verdana, Arial, sans-serif; box-sizing: border-box; padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */ width: 10em; /* 100px */ border: 0.2em solid #000; /* 2px */ border-radius: 0.4em; /* 4px */ box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */ background: #f0f0f0; background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0); } .select .value { display: inline-block; width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; vertical-align: top; } .select:after { content: "▼"; position: absolute; z-index: 1; height: 100%; width: 2em; /* 20px */ top: 0; right: 0; padding-top: 0.1em; box-sizing: border-box; text-align: center; border-left: 0.2em solid #000; border-radius: 0 0.1em 0.1em 0; background-color: #000; color: #fff; } .select .optList { z-index: 2; list-style: none; margin: 0; padding: 0; background: #f0f0f0; border: 0.2em solid #000; border-top-width: 0.1em; border-radius: 0 0 0.4em 0.4em; box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4); box-sizing: border-box; min-width: 100%; max-height: 10em; /* 100px */ overflow-y: auto; overflow-x: hidden; } .select .option { padding: 0.2em 0.3em; } .select .highlight { background: #000; color: #ffffff; } </code></pre></div> <p>must be provided</p> <h4 id="active_state">Active state</h4> <div class="code-example"><pre class="brush: html hidden notranslate"><code>&lt;div class="select active"&gt; &lt;span class="value"&gt;Cherry&lt;/span&gt; &lt;ul class="optList hidden"&gt; &lt;li class="option"&gt;Cherry&lt;/li&gt; &lt;li class="option"&gt;Lemon&lt;/li&gt; &lt;li class="option"&gt;Banana&lt;/li&gt; &lt;li class="option"&gt;Strawberry&lt;/li&gt; &lt;li class="option"&gt;Apple&lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; </code></pre></div> <div class="code-example"><pre class="brush: css hidden notranslate"><code>.select { position: relative; display: inline-block; } .select.active, .select:focus { box-shadow: 0 0 3px 1px #227755; outline: none; } .select .optList { position: absolute; top: 100%; left: 0; } .select .optList.hidden { max-height: 0; visibility: hidden; } .select { font-size: 0.625em; /* 10px */ font-family: Verdana, Arial, sans-serif; box-sizing: border-box; padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */ width: 10em; /* 100px */ border: 0.2em solid #000; /* 2px */ border-radius: 0.4em; /* 4px */ box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */ background: #f0f0f0; background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0); } .select .value { display: inline-block; width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; vertical-align: top; } .select:after { content: "▼"; position: absolute; z-index: 1; height: 100%; width: 2em; /* 20px */ top: 0; right: 0; padding-top: 0.1em; box-sizing: border-box; text-align: center; border-left: 0.2em solid #000; border-radius: 0 0.1em 0.1em 0; background-color: #000; color: #fff; } .select .optList { z-index: 2; list-style: none; margin: 0; padding: 0; background: #f0f0f0; border: 0.2em solid #000; border-top-width: 0.1em; border-radius: 0 0 0.4em 0.4em; box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4); box-sizing: border-box; min-width: 100%; max-height: 10em; /* 100px */ overflow-y: auto; overflow-x: hidden; } .select .option { padding: 0.2em 0.3em; } .select .highlight { background: #000; color: #ffffff; } </code></pre></div> <p>must be provided</p> <h4 id="open_state">Open state</h4> <div class="code-example"><pre class="brush: html hidden notranslate"><code>&lt;div class="select active"&gt; &lt;span class="value"&gt;Cherry&lt;/span&gt; &lt;ul class="optList"&gt; &lt;li class="option highlight"&gt;Cherry&lt;/li&gt; &lt;li class="option"&gt;Lemon&lt;/li&gt; &lt;li class="option"&gt;Banana&lt;/li&gt; &lt;li class="option"&gt;Strawberry&lt;/li&gt; &lt;li class="option"&gt;Apple&lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; </code></pre></div> <div class="code-example"><pre class="brush: css hidden notranslate"><code>.select { position: relative; display: inline-block; } .select.active, .select:focus { box-shadow: 0 0 3px 1px #227755; outline: none; } .select .optList { position: absolute; top: 100%; left: 0; } .select .optList.hidden { max-height: 0; visibility: hidden; } .select { font-size: 0.625em; /* 10px */ font-family: Verdana, Arial, sans-serif; box-sizing: border-box; padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */ width: 10em; /* 100px */ border: 0.2em solid #000; /* 2px */ border-radius: 0.4em; /* 4px */ box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */ background: #f0f0f0; background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0); } .select .value { display: inline-block; width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; vertical-align: top; } .select:after { content: "▼"; position: absolute; z-index: 1; height: 100%; width: 2em; /* 20px */ top: 0; right: 0; padding-top: 0.1em; box-sizing: border-box; text-align: center; border-left: 0.2em solid #000; border-radius: 0 0.1em 0.1em 0; background-color: #000; color: #fff; } .select .optList { z-index: 2; list-style: none; margin: 0; padding: 0; background: #f0f0f0; border: 0.2em solid #000; border-top-width: 0.1em; border-radius: 0 0 0.4em 0.4em; box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4); box-sizing: border-box; min-width: 100%; max-height: 10em; /* 100px */ overflow-y: auto; overflow-x: hidden; } .select .option { padding: 0.2em 0.3em; } .select .highlight { background: #000; color: #fff; } </code></pre></div> <p>must be provided</p></div></section><section aria-labelledby="bring_your_widget_to_life_with_javascript"><h2 id="bring_your_widget_to_life_with_javascript"><a href="#bring_your_widget_to_life_with_javascript">Bring your widget to life with JavaScript</a></h2><div class="section-content"><p>Now that our design and structure are ready, we can write the JavaScript code to make the widget actually work.</p> <div class="notecard warning"> <p><strong>Advertencia:</strong> The following code is educational and should not be used as-is. Among many things, as we'll see, it is not future-proof and it will not work on legacy browsers. It also has redundant parts that should be optimized in production code.</p> </div> <div class="notecard note"> <p><strong>Nota:</strong> Creating reusable widgets is something that can be a bit tricky. The <a href="https://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html" class="external" target="_blank">W3C Web Component draft</a> is one of the answers to this specific issue. <a href="http://x-tags.org/" class="external" target="_blank">The X-Tag project</a> is a test implementation of this specification; we encourage you to take a look at it.</p> </div></div></section><section aria-labelledby="why_isnt_it_working"><h3 id="why_isnt_it_working"><a href="#why_isnt_it_working">Why isn't it working?</a></h3><div class="section-content"><p>Before we start, it's important to remember something very important about JavaScript: inside a browser, <strong>it's an unreliable technology</strong>. When you are building custom widgets, you'll have to rely on JavaScript because it's a necessary thread to tie everything together. However, there are many cases in which JavaScript isn't able to run in the browser:</p> <ul> <li>The user has turned off JavaScript: This is the most unusual case ever; very few people turn off JavaScript nowadays.</li> <li>The script is not loading. This is one of the most common cases, especially in the mobile world where the network is not very reliable.</li> <li>The script is buggy. You should always consider this possibility.</li> <li>The script is in conflict with a third party script. This can happen with tracking scripts or any bookmarklets the user uses.</li> <li>The script is in conflict with, or is affected by, a browser extension (such as Firefox's <a href="https://addons.mozilla.org/fr/firefox/addon/noscript/" class="external" target="_blank">NoScript</a> extension or Chrome's <a href="https://chrome.google.com/webstore/detail/notscripts/odjhifogjcknibkahlpidmdajjpkkcfn" class="external" target="_blank">NotScripts</a> extension).</li> <li>The user is using a legacy browser, and one of the features you require is not supported. This will happen frequently when you make use of cutting-edge APIs.</li> </ul> <p>Because of these risks, it's really important to seriously consider what will happen if JavaScript isn't working. Dealing in detail with this issue is out of the scope of this article because it's closely linked to how you want to make your script generic and reusable, but we'll consider the basics of this in our example.</p> <p>In our example, if our JavaScript code isn't running, we'll fall back to displaying a standard <a href="/es/docs/Web/HTML/Element/select"><code>&lt;select&gt;</code></a> element. To achieve this, we need two things.</p> <p>First, we need to add a regular <a href="/es/docs/Web/HTML/Element/select"><code>&lt;select&gt;</code></a> element before each use of our custom widget. This is actually also required in order to be able to send data from our custom widget along with the rest of our form data; more about this later.</p> <div class="code-example"><div class="example-header"><span class="language-name">html</span></div><pre class="brush: html notranslate"><code>&lt;body class="no-widget"&gt; &lt;form&gt; &lt;select name="myFruit"&gt; &lt;option&gt;Cherry&lt;/option&gt; &lt;option&gt;Lemon&lt;/option&gt; &lt;option&gt;Banana&lt;/option&gt; &lt;option&gt;Strawberry&lt;/option&gt; &lt;option&gt;Apple&lt;/option&gt; &lt;/select&gt; &lt;div class="select"&gt; &lt;span class="value"&gt;Cherry&lt;/span&gt; &lt;ul class="optList hidden"&gt; &lt;li class="option"&gt;Cherry&lt;/li&gt; &lt;li class="option"&gt;Lemon&lt;/li&gt; &lt;li class="option"&gt;Banana&lt;/li&gt; &lt;li class="option"&gt;Strawberry&lt;/li&gt; &lt;li class="option"&gt;Apple&lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; &lt;/form&gt; &lt;/body&gt; </code></pre></div> <p>Second, we need two new classes to let us hide the unneeded element (that is, the "real" <a href="/es/docs/Web/HTML/Element/select"><code>&lt;select&gt;</code></a> element if our script isn't running, or the custom widget if it is running). Note that by default, our HTML code hides our custom widget.</p> <div class="code-example"><div class="example-header"><span class="language-name">css</span></div><pre class="brush: css notranslate"><code>.widget select, .no-widget .select { /* This CSS selector basically says: - either we have set the body class to "widget" and thus we hide the actual <a href="/es/docs/Web/HTML/Element/select"><code>&lt;select&gt;</code></a> element - or we have not changed the body class, therefore the body class is still "no-widget", so the elements whose class is "select" must be hidden */ position: absolute; left: -5000em; height: 0; overflow: hidden; } </code></pre></div> <p>Now we just need a JavaScript switch to determine if the script is running or not. This switch is very simple: if at page load time our script is running, it will remove the <code>no-widget</code> class and add the <code>widget</code> class, thereby swapping the visibility of the <a href="/es/docs/Web/HTML/Element/select"><code>&lt;select&gt;</code></a> element and of the custom widget.</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>window.addEventListener("load", function () { document.body.classList.remove("no-widget"); document.body.classList.add("widget"); }); </code></pre></div> <h4 id="without_js">Without JS</h4> <p>Check out the <a href="/en-US/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls/Example_2#no_js" class="only-in-en-us">full source code</a>.</p> <div class="code-example"><pre class="brush: html hidden notranslate"><code>&lt;form class="no-widget"&gt; &lt;select name="myFruit"&gt; &lt;option&gt;Cherry&lt;/option&gt; &lt;option&gt;Lemon&lt;/option&gt; &lt;option&gt;Banana&lt;/option&gt; &lt;option&gt;Strawberry&lt;/option&gt; &lt;option&gt;Apple&lt;/option&gt; &lt;/select&gt; &lt;div class="select"&gt; &lt;span class="value"&gt;Cherry&lt;/span&gt; &lt;ul class="optList hidden"&gt; &lt;li class="option"&gt;Cherry&lt;/li&gt; &lt;li class="option"&gt;Lemon&lt;/li&gt; &lt;li class="option"&gt;Banana&lt;/li&gt; &lt;li class="option"&gt;Strawberry&lt;/li&gt; &lt;li class="option"&gt;Apple&lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; &lt;/form&gt; </code></pre></div> <div class="code-example"><pre class="brush: css hidden notranslate"><code>.widget select, .no-widget .select { position: absolute; left: -5000em; height: 0; overflow: hidden; } </code></pre></div> <div class="code-example"><div class="example-header"></div><iframe class="sample-code-frame" title="Without JS sample" id="frame_without_js" width="120" height="130" src="about:blank" data-live-path="/es/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls/" data-live-id="without_js" sandbox="allow-same-origin allow-scripts" loading="lazy"></iframe></div> <h4 id="with_js">With JS</h4> <p>Check out the <a href="/en-US/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls/Example_2#js" class="only-in-en-us">full source code</a>.</p> <div class="code-example"><pre class="brush: html hidden notranslate"><code>&lt;form class="no-widget"&gt; &lt;select name="myFruit"&gt; &lt;option&gt;Cherry&lt;/option&gt; &lt;option&gt;Lemon&lt;/option&gt; &lt;option&gt;Banana&lt;/option&gt; &lt;option&gt;Strawberry&lt;/option&gt; &lt;option&gt;Apple&lt;/option&gt; &lt;/select&gt; &lt;div class="select"&gt; &lt;span class="value"&gt;Cherry&lt;/span&gt; &lt;ul class="optList hidden"&gt; &lt;li class="option"&gt;Cherry&lt;/li&gt; &lt;li class="option"&gt;Lemon&lt;/li&gt; &lt;li class="option"&gt;Banana&lt;/li&gt; &lt;li class="option"&gt;Strawberry&lt;/li&gt; &lt;li class="option"&gt;Apple&lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; &lt;/form&gt; </code></pre></div> <div class="code-example"><pre class="brush: css hidden notranslate"><code>.widget select, .no-widget .select { position: absolute; left: -5000em; height: 0; overflow: hidden; } .select { position: relative; display: inline-block; } .select.active, .select:focus { box-shadow: 0 0 3px 1px #227755; outline: none; } .select .optList { position: absolute; top: 100%; left: 0; } .select .optList.hidden { max-height: 0; visibility: hidden; } .select { font-size: 0.625em; /* 10px */ font-family: Verdana, Arial, sans-serif; box-sizing: border-box; padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */ width: 10em; /* 100px */ border: 0.2em solid #000; /* 2px */ border-radius: 0.4em; /* 4px */ box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */ background: #f0f0f0; background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0); } .select .value { display: inline-block; width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; vertical-align: top; } .select:after { content: "▼"; position: absolute; z-index: 1; height: 100%; width: 2em; /* 20px */ top: 0; right: 0; padding-top: 0.1em; box-sizing: border-box; text-align: center; border-left: 0.2em solid #000; border-radius: 0 0.1em 0.1em 0; background-color: #000; color: #fff; } .select .optList { z-index: 2; list-style: none; margin: 0; padding: 0; background: #f0f0f0; border: 0.2em solid #000; border-top-width: 0.1em; border-radius: 0 0 0.4em 0.4em; box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4); box-sizing: border-box; min-width: 100%; max-height: 10em; /* 100px */ overflow-y: auto; overflow-x: hidden; } .select .option { padding: 0.2em 0.3em; } .select .highlight { background: #000; color: #ffffff; } </code></pre></div> <div class="code-example"><pre class="brush: js hidden notranslate"><code>window.addEventListener("load", () =&gt; { const form = document.querySelector("form"); form.classList.remove("no-widget"); form.classList.add("widget"); }); </code></pre></div> <p>must be provided</p> <div class="notecard note"> <p><strong>Nota:</strong> If you really want to make your code generic and reusable, instead of doing a class switch it's far better to just add the widget class to hide the <a href="/es/docs/Web/HTML/Element/select"><code>&lt;select&gt;</code></a> elements, and to dynamically add the DOM tree representing the custom widget after every <a href="/es/docs/Web/HTML/Element/select"><code>&lt;select&gt;</code></a> element in the page.</p> </div></div></section><section aria-labelledby="making_the_job_easier"><h3 id="making_the_job_easier"><a href="#making_the_job_easier">Making the job easier</a></h3><div class="section-content"><p>In the code we are about to build, we will use the standard DOM API to do all the work we need. However, although DOM API support has gotten much better in browsers, there are always issues with legacy browsers (especially with good old Internet Explorer).</p> <p>If you want to avoid trouble with legacy browsers, there are two ways to do so: using a dedicated framework such as <a href="https://jquery.com/" class="external" target="_blank">jQuery</a>, <a href="https://github.com/julienw/dollardom" class="external" target="_blank">$dom</a>, <a href="http://prototypejs.org/" class="external" target="_blank">prototype</a>, <a href="https://dojotoolkit.org/" class="external" target="_blank">Dojo</a>, <a href="https://yuilibrary.com/" class="external" target="_blank">YUI</a>, or the like, or by polyfilling the missing feature you want to use (which can easily be done through conditional loading, with the <a href="https://yepnopejs.com/" class="external" target="_blank">yepnope</a> library for example).</p> <p>The features we plan to use are the following (ordered from the riskiest to the safest):</p> <ol> <li><a href="/es/docs/Web/API/Element/classList" title="classList"><code>classList</code></a></li> <li><a href="/es/docs/Web/API/EventTarget/addEventListener" title="addEventListener"><code>addEventListener</code></a></li> <li><a href="/es/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach"><code>forEach</code></a> (This is not DOM but modern JavaScript)</li> <li><a href="/es/docs/Web/API/Element/querySelector" title="querySelector"><code>querySelector</code></a> and <a href="/en-US/docs/Web/API/Element/querySelectorAll" title="querySelectorAll" class="only-in-en-us"><code>querySelectorAll</code></a></li> </ol> <p>Beyond the availability of those specific features, there is still one issue remaining before starting. The object returned by the <a href="/en-US/docs/Web/API/Element/querySelectorAll" title="querySelectorAll()" class="only-in-en-us"><code>querySelectorAll()</code></a> function is a <a href="/es/docs/Web/API/NodeList"><code>NodeList</code></a> rather than an <a href="/es/docs/Web/JavaScript/Reference/Global_Objects/Array"><code>Array</code></a>. This is important because <code>Array</code> objects support the <a href="/es/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach"><code>forEach</code></a> function, but <a href="/es/docs/Web/API/NodeList"><code>NodeList</code></a> doesn't. Because <a href="/es/docs/Web/API/NodeList"><code>NodeList</code></a> really looks like an <code>Array</code> and because <code>forEach</code> is so convenient to use, we can easily add the support of <code>forEach</code> to <a href="/es/docs/Web/API/NodeList"><code>NodeList</code></a> in order to make our life easier, like so:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>NodeList.prototype.forEach = function (callback) { Array.prototype.forEach.call(this, callback); }; </code></pre></div> <p>We weren't kidding when we said it's easy to do.</p></div></section><section aria-labelledby="building_event_callbacks"><h3 id="building_event_callbacks"><a href="#building_event_callbacks">Building event callbacks</a></h3><div class="section-content"><p>The ground is ready, we can now start to define all the functions that will be used each time the user interacts with our widget.</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// This function will be used each time we want to deactivate a custom widget // It takes one parameter // select : the DOM node with the `select` class to deactivate function deactivateSelect(select) { // If the widget is not active there is nothing to do if (!select.classList.contains("active")) return; // We need to get the list of options for the custom widget var optList = select.querySelector(".optList"); // We close the list of option optList.classList.add("hidden"); // and we deactivate the custom widget itself select.classList.remove("active"); } // This function will be used each time the user wants to (de)activate the widget // It takes two parameters: // select : the DOM node with the `select` class to activate // selectList : the list of all the DOM nodes with the `select` class function activeSelect(select, selectList) { // If the widget is already active there is nothing to do if (select.classList.contains("active")) return; // We have to turn off the active state on all custom widgets // Because the deactivateSelect function fulfill all the requirement of the // forEach callback function, we use it directly without using an intermediate // anonymous function. selectList.forEach(deactivateSelect); // And we turn on the active state for this specific widget select.classList.add("active"); } // This function will be used each time the user wants to open/closed the list of options // It takes one parameter: // select : the DOM node with the list to toggle function toggleOptList(select) { // The list is kept from the widget var optList = select.querySelector(".optList"); // We change the class of the list to show/hide it optList.classList.toggle("hidden"); } // This function will be used each time we need to highlight an option // It takes two parameters: // select : the DOM node with the `select` class containing the option to highlight // option : the DOM node with the `option` class to highlight function highlightOption(select, option) { // We get the list of all option available for our custom select element var optionList = select.querySelectorAll(".option"); // We remove the highlight from all options optionList.forEach(function (other) { other.classList.remove("highlight"); }); // We highlight the right option option.classList.add("highlight"); } </code></pre></div> <p>That's all you need in order to handle the various states of the custom widget.</p> <p>Next, we bind these functions to the appropriate events:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// We handle the event binding when the document is loaded. window.addEventListener("load", function () { var selectList = document.querySelectorAll(".select"); // Each custom widget needs to be initialized selectList.forEach(function (select) { // as well as all its `option` elements var optionList = select.querySelectorAll(".option"); // Each time a user hovers their mouse over an option, we highlight the given option optionList.forEach(function (option) { option.addEventListener("mouseover", function () { // Note: the `select` and `option` variable are closures // available in the scope of our function call. highlightOption(select, option); }); }); // Each times the user click on a custom select element select.addEventListener("click", function (event) { // Note: the `select` variable is a closure // available in the scope of our function call. // We toggle the visibility of the list of options toggleOptList(select); }); // In case the widget gain focus // The widget gains the focus each time the user clicks on it or each time // they use the tabulation key to access the widget select.addEventListener("focus", function (event) { // Note: the `select` and `selectList` variable are closures // available in the scope of our function call. // We activate the widget activeSelect(select, selectList); }); // In case the widget loose focus select.addEventListener("blur", function (event) { // Note: the `select` variable is a closure // available in the scope of our function call. // We deactivate the widget deactivateSelect(select); }); }); }); </code></pre></div> <p>At that point, our widget will change state according to our design, but its value doesn't get updated yet. We'll handle that next.</p> <h4 id="live_example">Live example</h4> <div class="code-example"><pre class="brush: html hidden notranslate"><code>&lt;form class="no-widget"&gt; &lt;select name="myFruit" tabindex="-1"&gt; &lt;option&gt;Cherry&lt;/option&gt; &lt;option&gt;Lemon&lt;/option&gt; &lt;option&gt;Banana&lt;/option&gt; &lt;option&gt;Strawberry&lt;/option&gt; &lt;option&gt;Apple&lt;/option&gt; &lt;/select&gt; &lt;div class="select" tabindex="0"&gt; &lt;span class="value"&gt;Cherry&lt;/span&gt; &lt;ul class="optList hidden"&gt; &lt;li class="option"&gt;Cherry&lt;/li&gt; &lt;li class="option"&gt;Lemon&lt;/li&gt; &lt;li class="option"&gt;Banana&lt;/li&gt; &lt;li class="option"&gt;Strawberry&lt;/li&gt; &lt;li class="option"&gt;Apple&lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; &lt;/form&gt; </code></pre></div> <div class="code-example"><pre class="brush: css hidden notranslate"><code>.widget select, .no-widget .select { position: absolute; left: -5000em; height: 0; overflow: hidden; } .select { position: relative; display: inline-block; } .select.active, .select:focus { box-shadow: 0 0 3px 1px #227755; outline: none; } .select .optList { position: absolute; top: 100%; left: 0; } .select .optList.hidden { max-height: 0; visibility: hidden; } .select { font-size: 0.625em; /* 10px */ font-family: Verdana, Arial, sans-serif; box-sizing: border-box; padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */ width: 10em; /* 100px */ border: 0.2em solid #000; /* 2px */ border-radius: 0.4em; /* 4px */ box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */ background: #f0f0f0; background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0); } .select .value { display: inline-block; width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; vertical-align: top; } .select:after { content: "▼"; position: absolute; z-index: 1; height: 100%; width: 2em; /* 20px */ top: 0; right: 0; padding-top: 0.1em; box-sizing: border-box; text-align: center; border-left: 0.2em solid #000; border-radius: 0 0.1em 0.1em 0; background-color: #000; color: #fff; } .select .optList { z-index: 2; list-style: none; margin: 0; padding: 0; background: #f0f0f0; border: 0.2em solid #000; border-top-width: 0.1em; border-radius: 0 0 0.4em 0.4em; box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4); box-sizing: border-box; min-width: 100%; max-height: 10em; /* 100px */ overflow-y: auto; overflow-x: hidden; } .select .option { padding: 0.2em 0.3em; } .select .highlight { background: #000; color: #ffffff; } </code></pre></div> <div class="code-example"><pre class="brush: js hidden notranslate"><code>function deactivateSelect(select) { if (!select.classList.contains("active")) return; const optList = select.querySelector(".optList"); optList.classList.add("hidden"); select.classList.remove("active"); } function activeSelect(select, selectList) { if (select.classList.contains("active")) return; selectList.forEach(deactivateSelect); select.classList.add("active"); } function toggleOptList(select, show) { const optList = select.querySelector(".optList"); optList.classList.toggle("hidden"); } function highlightOption(select, option) { const optionList = select.querySelectorAll(".option"); optionList.forEach((other) =&gt; { other.classList.remove("highlight"); }); option.classList.add("highlight"); } window.addEventListener("load", () =&gt; { const form = document.querySelector("form"); form.classList.remove("no-widget"); form.classList.add("widget"); }); window.addEventListener("load", () =&gt; { const selectList = document.querySelectorAll(".select"); selectList.forEach((select) =&gt; { const optionList = select.querySelectorAll(".option"); optionList.forEach((option) =&gt; { option.addEventListener("mouseover", () =&gt; { highlightOption(select, option); }); }); select.addEventListener( "click", (event) =&gt; { toggleOptList(select); }, false, ); select.addEventListener("focus", (event) =&gt; { activeSelect(select, selectList); }); select.addEventListener("blur", (event) =&gt; { deactivateSelect(select); }); select.addEventListener("keyup", (event) =&gt; { if (event.keyCode === 27) { deactivateSelect(select); } }); }); }); </code></pre></div> <figure class="table-container"><table> <thead> <tr> <th>Live example</th> </tr> </thead> <tbody> <tr> <td>must be provided</td> </tr> </tbody> </table></figure> <pre class="notranslate"> | </pre> <p>| <a href="/en-US/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls/Example_3" class="only-in-en-us">Check out the source code</a> |</p></div></section><section aria-labelledby="handling_the_widgets_value"><h3 id="handling_the_widgets_value"><a href="#handling_the_widgets_value">Handling the widget's value</a></h3><div class="section-content"><p>Now that our widget is working, we have to add code to update its value according to user input and make it possible to send the value along with form data.</p> <p>The easiest way to do this is to use a native widget under the hood. Such a widget will keep track of the value with all the built-in controls provided by the browser, and the value will be sent as usual when a form is submitted. There's no point in reinventing the wheel when we can have all this done for us.</p> <p>As seen previously, we already use a native select widget as a fallback for accessibility reasons; we can simply synchronize its value with that of our custom widget:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// This function updates the displayed value and synchronizes it with the native widget. // It takes two parameters: // select : the DOM node with the class `select` containing the value to update // index : the index of the value to be selected function updateValue(select, index) { // We need to get the native widget for the given custom widget // In our example, that native widget is a sibling of the custom widget var nativeWidget = select.previousElementSibling; // We also need to get the value placeholder of our custom widget var value = select.querySelector(".value"); // And we need the whole list of options var optionList = select.querySelectorAll(".option"); // We set the selected index to the index of our choice nativeWidget.selectedIndex = index; // We update the value placeholder accordingly value.innerHTML = optionList[index].innerHTML; // And we highlight the corresponding option of our custom widget highlightOption(select, optionList[index]); } // This function returns the current selected index in the native widget // It takes one parameter: // select : the DOM node with the class `select` related to the native widget function getIndex(select) { // We need to access the native widget for the given custom widget // In our example, that native widget is a sibling of the custom widget var nativeWidget = select.previousElementSibling; return nativeWidget.selectedIndex; } </code></pre></div> <p>With these two functions, we can bind the native widgets to the custom ones:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// We handle event binding when the document is loaded. window.addEventListener("load", function () { var selectList = document.querySelectorAll(".select"); // Each custom widget needs to be initialized selectList.forEach(function (select) { var optionList = select.querySelectorAll(".option"), selectedIndex = getIndex(select); // We make our custom widget focusable select.tabIndex = 0; // We make the native widget no longer focusable select.previousElementSibling.tabIndex = -1; // We make sure that the default selected value is correctly displayed updateValue(select, selectedIndex); // Each time a user clicks on an option, we update the value accordingly optionList.forEach(function (option, index) { option.addEventListener("click", function (event) { updateValue(select, index); }); }); // Each time a user uses their keyboard on a focused widget, we update the value accordingly select.addEventListener("keyup", function (event) { var length = optionList.length, index = getIndex(select); // When the user hits the down arrow, we jump to the next option if (event.keyCode === 40 &amp;&amp; index &lt; length - 1) { index++; } // When the user hits the up arrow, we jump to the previous option if (event.keyCode === 38 &amp;&amp; index &gt; 0) { index--; } updateValue(select, index); }); }); }); </code></pre></div> <p>In the code above, it's worth noting the use of the <a href="/en-US/docs/Web/API/HTMLElement/tabIndex" class="only-in-en-us"><code>tabIndex</code></a> property. Using this property is necessary to ensure that the native widget will never gain focus, and to make sure that our custom widget gains focus when the user uses his keyboard or his mouse.</p> <p>With that, we're done! Here's the result:</p> <div class="code-example"><pre class="brush: html hidden notranslate"><code>&lt;form class="no-widget"&gt; &lt;select name="myFruit"&gt; &lt;option&gt;Cherry&lt;/option&gt; &lt;option&gt;Lemon&lt;/option&gt; &lt;option&gt;Banana&lt;/option&gt; &lt;option&gt;Strawberry&lt;/option&gt; &lt;option&gt;Apple&lt;/option&gt; &lt;/select&gt; &lt;div class="select"&gt; &lt;span class="value"&gt;Cherry&lt;/span&gt; &lt;ul class="optList hidden"&gt; &lt;li class="option"&gt;Cherry&lt;/li&gt; &lt;li class="option"&gt;Lemon&lt;/li&gt; &lt;li class="option"&gt;Banana&lt;/li&gt; &lt;li class="option"&gt;Strawberry&lt;/li&gt; &lt;li class="option"&gt;Apple&lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; &lt;/form&gt; </code></pre></div> <div class="code-example"><pre class="brush: css hidden notranslate"><code>.widget select, .no-widget .select { position: absolute; left: -5000em; height: 0; overflow: hidden; } .select { position: relative; display: inline-block; } .select.active, .select:focus { box-shadow: 0 0 3px 1px #227755; outline: none; } .select .optList { position: absolute; top: 100%; left: 0; } .select .optList.hidden { max-height: 0; visibility: hidden; } .select { font-size: 0.625em; /* 10px */ font-family: Verdana, Arial, sans-serif; box-sizing: border-box; padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */ width: 10em; /* 100px */ border: 0.2em solid #000; /* 2px */ border-radius: 0.4em; /* 4px */ box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */ background: #f0f0f0; background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0); } .select .value { display: inline-block; width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; vertical-align: top; } .select:after { content: "▼"; position: absolute; z-index: 1; height: 100%; width: 2em; /* 20px */ top: 0; right: 0; padding-top: 0.1em; box-sizing: border-box; text-align: center; border-left: 0.2em solid #000; border-radius: 0 0.1em 0.1em 0; background-color: #000; color: #fff; } .select .optList { z-index: 2; list-style: none; margin: 0; padding: 0; background: #f0f0f0; border: 0.2em solid #000; border-top-width: 0.1em; border-radius: 0 0 0.4em 0.4em; box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4); box-sizing: border-box; min-width: 100%; max-height: 10em; /* 100px */ overflow-y: auto; overflow-x: hidden; } .select .option { padding: 0.2em 0.3em; } .select .highlight { background: #000; color: #ffffff; } </code></pre></div> <div class="code-example"><pre class="brush: js hidden notranslate"><code>function deactivateSelect(select) { if (!select.classList.contains("active")) return; const optList = select.querySelector(".optList"); optList.classList.add("hidden"); select.classList.remove("active"); } function activeSelect(select, selectList) { if (select.classList.contains("active")) return; selectList.forEach(deactivateSelect); select.classList.add("active"); } function toggleOptList(select, show) { const optList = select.querySelector(".optList"); optList.classList.toggle("hidden"); } function highlightOption(select, option) { const optionList = select.querySelectorAll(".option"); optionList.forEach((other) =&gt; { other.classList.remove("highlight"); }); option.classList.add("highlight"); } function updateValue(select, index) { const nativeWidget = select.previousElementSibling; const value = select.querySelector(".value"); const optionList = select.querySelectorAll(".option"); nativeWidget.selectedIndex = index; value.innerHTML = optionList[index].innerHTML; highlightOption(select, optionList[index]); } function getIndex(select) { const nativeWidget = select.previousElementSibling; return nativeWidget.selectedIndex; } window.addEventListener("load", () =&gt; { const form = document.querySelector("form"); form.classList.remove("no-widget"); form.classList.add("widget"); }); window.addEventListener("load", () =&gt; { const selectList = document.querySelectorAll(".select"); selectList.forEach((select) =&gt; { const optionList = select.querySelectorAll(".option"); optionList.forEach((option) =&gt; { option.addEventListener("mouseover", () =&gt; { highlightOption(select, option); }); }); select.addEventListener("click", (event) =&gt; { toggleOptList(select); }); select.addEventListener("focus", (event) =&gt; { activeSelect(select, selectList); }); select.addEventListener("blur", (event) =&gt; { deactivateSelect(select); }); }); }); window.addEventListener("load", () =&gt; { const selectList = document.querySelectorAll(".select"); selectList.forEach((select) =&gt; { const optionList = select.querySelectorAll(".option"); const selectedIndex = getIndex(select); select.tabIndex = 0; select.previousElementSibling.tabIndex = -1; updateValue(select, selectedIndex); optionList.forEach((option, index) =&gt; { option.addEventListener("click", (event) =&gt; { updateValue(select, index); }); }); select.addEventListener("keyup", (event) =&gt; { let index = getIndex(select); if (event.key === "Escape") { deactivateSelect(select); } if (event.key === "ArrowDown" &amp;&amp; index &lt; optionList.length - 1) { index++; } if (event.key === "ArrowUp" &amp;&amp; index &gt; 0) { index--; } updateValue(select, index); }); }); }); </code></pre></div> <figure class="table-container"><table> <thead> <tr> <th>Live example</th> </tr> </thead> <tbody> <tr> <td>must be provided</td> </tr> </tbody> </table></figure> <pre class="notranslate"> | </pre> <p>| <a href="/en-US/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls/Example_4" class="only-in-en-us">Check out the source code</a> |</p> <p>But wait a second, are we really done?</p></div></section><section aria-labelledby="make_it_accessible"><h2 id="make_it_accessible"><a href="#make_it_accessible">Make it accessible</a></h2><div class="section-content"><p>We have built something that works and though we're far from a fully-featured select box, it works nicely. But what we've done is nothing more than fiddle with the DOM. It has no real semantics, and even though it looks like a select box, from the browser's point of view it isn't one, so assistive technologies won't be able to understand it's a select box. In short, this pretty new select box isn't accessible!</p> <p>Fortunately, there is a solution and it's called <a href="/es/docs/Web/Accessibility/ARIA">ARIA</a>. ARIA stands for "Accessible Rich Internet Application", and it's <a href="https://www.w3.org/TR/wai-aria/" class="external" target="_blank">a W3C specification</a> specifically designed for what we are doing here: making web applications and custom widgets accessible. It's basically a set of attributes that extend HTML so that we can better describe roles, states and properties as though the element we've just devised was the native element it tries to pass for. Using these attributes is dead simple, so let's do it.</p></div></section><section aria-labelledby="the_role_attribute"><h3 id="the_role_attribute"><a href="#the_role_attribute">The <code>role</code> attribute</a></h3><div class="section-content"><p>The key attribute used by <a href="/es/docs/Web/Accessibility/ARIA">ARIA</a> is the <a href="/es/docs/Web/Accessibility/ARIA/ARIA_Techniques"><code>role</code></a> attribute. The <a href="/es/docs/Web/Accessibility/ARIA/ARIA_Techniques"><code>role</code></a> attribute accepts a value that defines what an element is used for. Each role defines its own requirements and behaviors. In our example, we will use the <a href="/en-US/docs/Web/Accessibility/ARIA/Roles/listbox_role" class="only-in-en-us"><code>listbox</code></a> role. It's a "composite role", which means elements with that role expect to have children, each with a specific role (in this case, at least one child with the <code>option</code> role).</p> <p>It's also worth noting that ARIA defines roles that are applied by default to standard HTML markup. For example, the <a href="/es/docs/Web/HTML/Element/table"><code>&lt;table&gt;</code></a> element matches the role <code>grid</code>, and the <a href="/es/docs/Web/HTML/Element/ul"><code>&lt;ul&gt;</code></a> element matches the role <code>list</code>. Because we use a <a href="/es/docs/Web/HTML/Element/ul"><code>&lt;ul&gt;</code></a> element, we want to make sure the <code>listbox</code> role of our widget will supersede the <code>list</code> role of the <a href="/es/docs/Web/HTML/Element/ul"><code>&lt;ul&gt;</code></a> element. To that end, we will use the role <code>presentation</code>. This role is designed to let us indicate that an element has no special meaning, and is used solely to present information. We will apply it to our <a href="/es/docs/Web/HTML/Element/ul"><code>&lt;ul&gt;</code></a> element.</p> <p>To support the <a href="/en-US/docs/Web/Accessibility/ARIA/Roles/listbox_role" class="only-in-en-us"><code>listbox</code></a> role, we just have to update our HTML like this:</p> <div class="code-example"><div class="example-header"><span class="language-name">html</span></div><pre class="brush: html notranslate"><code>&lt;!-- We add the role="listbox" attribute to our top element --&gt; &lt;div class="select" role="listbox"&gt; &lt;span class="value"&gt;Cherry&lt;/span&gt; &lt;!-- We also add the role="presentation" to the ul element --&gt; &lt;ul class="optList" role="presentation"&gt; &lt;!-- And we add the role="option" attribute to all the li elements --&gt; &lt;li role="option" class="option"&gt;Cherry&lt;/li&gt; &lt;li role="option" class="option"&gt;Lemon&lt;/li&gt; &lt;li role="option" class="option"&gt;Banana&lt;/li&gt; &lt;li role="option" class="option"&gt;Strawberry&lt;/li&gt; &lt;li role="option" class="option"&gt;Apple&lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; </code></pre></div> <div class="notecard note"> <p><strong>Nota:</strong> Including both the <code>role</code> attribute and a <code>class</code> attribute is only necessary if you want to support legacy browsers that do not support the <a href="/es/docs/Web/CSS/Attribute_selectors">CSS attribute selectors</a>.</p> </div></div></section><section aria-labelledby="the_aria-selected_attribute"><h3 id="the_aria-selected_attribute"><a href="#the_aria-selected_attribute">The <code>aria-selected</code> attribute</a></h3><div class="section-content"><p>Using the <a href="/es/docs/Web/Accessibility/ARIA/ARIA_Techniques"><code>role</code></a> attribute is not enough. <a href="/es/docs/Web/Accessibility/ARIA">ARIA</a> also provides many states and property attributes. The more and better you use them, the better your widget will be understood by assistive technologies. In our case, we will limit our usage to one attribute: <code>aria-selected</code>.</p> <p>The <code>aria-selected</code> attribute is used to mark which option is currently selected; this lets assistive technologies inform the user what the current selection is. We will use it dynamically with JavaScript to mark the selected option each time the user chooses one. To that end, we need to revise our <code>updateValue()</code> function:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>function updateValue(select, index) { var nativeWidget = select.previousElementSibling; var value = select.querySelector(".value"); var optionList = select.querySelectorAll(".option"); // We make sure that all the options are not selected optionList.forEach(function (other) { other.setAttribute("aria-selected", "false"); }); // We make sure the chosen option is selected optionList[index].setAttribute("aria-selected", "true"); nativeWidget.selectedIndex = index; value.innerHTML = optionList[index].innerHTML; highlightOption(select, optionList[index]); } </code></pre></div> <p>Here is the final result of all these changes (you'll get a better feel for this by trying it with an assistive technology such as <a href="http://www.nvda-project.org/" class="external" target="_blank">NVDA</a> or <a href="https://www.apple.com/accessibility/voiceover/" class="external" target="_blank">VoiceOver</a>):</p> <div class="code-example"><pre class="brush: html hidden notranslate"><code>&lt;form class="no-widget"&gt; &lt;select name="myFruit"&gt; &lt;option&gt;Cherry&lt;/option&gt; &lt;option&gt;Lemon&lt;/option&gt; &lt;option&gt;Banana&lt;/option&gt; &lt;option&gt;Strawberry&lt;/option&gt; &lt;option&gt;Apple&lt;/option&gt; &lt;/select&gt; &lt;div class="select" role="listbox"&gt; &lt;span class="value"&gt;Cherry&lt;/span&gt; &lt;ul class="optList hidden" role="presentation"&gt; &lt;li class="option" role="option" aria-selected="true"&gt;Cherry&lt;/li&gt; &lt;li class="option" role="option"&gt;Lemon&lt;/li&gt; &lt;li class="option" role="option"&gt;Banana&lt;/li&gt; &lt;li class="option" role="option"&gt;Strawberry&lt;/li&gt; &lt;li class="option" role="option"&gt;Apple&lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; &lt;/form&gt; </code></pre></div> <div class="code-example"><pre class="brush: css hidden notranslate"><code>.widget select, .no-widget .select { position: absolute; left: -5000em; height: 0; overflow: hidden; } .select { position: relative; display: inline-block; } .select.active, .select:focus { box-shadow: 0 0 3px 1px #227755; outline: none; } .select .optList { position: absolute; top: 100%; left: 0; } .select .optList.hidden { max-height: 0; visibility: hidden; } .select { font-size: 0.625em; /* 10px */ font-family: Verdana, Arial, sans-serif; box-sizing: border-box; padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */ width: 10em; /* 100px */ border: 0.2em solid #000; /* 2px */ border-radius: 0.4em; /* 4px */ box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */ background: #f0f0f0; background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0); } .select .value { display: inline-block; width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; vertical-align: top; } .select:after { content: "▼"; position: absolute; z-index: 1; height: 100%; width: 2em; /* 20px */ top: 0; right: 0; padding-top: 0.1em; box-sizing: border-box; text-align: center; border-left: 0.2em solid #000; border-radius: 0 0.1em 0.1em 0; background-color: #000; color: #fff; } .select .optList { z-index: 2; list-style: none; margin: 0; padding: 0; background: #f0f0f0; border: 0.2em solid #000; border-top-width: 0.1em; border-radius: 0 0 0.4em 0.4em; box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4); box-sizing: border-box; min-width: 100%; max-height: 10em; /* 100px */ overflow-y: auto; overflow-x: hidden; } .select .option { padding: 0.2em 0.3em; } .select .highlight { background: #000; color: #ffffff; } </code></pre></div> <div class="code-example"><pre class="brush: js hidden notranslate"><code>function deactivateSelect(select) { if (!select.classList.contains("active")) return; const optList = select.querySelector(".optList"); optList.classList.add("hidden"); select.classList.remove("active"); } function activeSelect(select, selectList) { if (select.classList.contains("active")) return; selectList.forEach(deactivateSelect); select.classList.add("active"); } function toggleOptList(select, show) { const optList = select.querySelector(".optList"); optList.classList.toggle("hidden"); } function highlightOption(select, option) { const optionList = select.querySelectorAll(".option"); optionList.forEach((other) =&gt; { other.classList.remove("highlight"); }); option.classList.add("highlight"); } function updateValue(select, index) { const nativeWidget = select.previousElementSibling; const value = select.querySelector(".value"); const optionList = select.querySelectorAll(".option"); optionList.forEach((other) =&gt; { other.setAttribute("aria-selected", "false"); }); optionList[index].setAttribute("aria-selected", "true"); nativeWidget.selectedIndex = index; value.innerHTML = optionList[index].innerHTML; highlightOption(select, optionList[index]); } function getIndex(select) { const nativeWidget = select.previousElementSibling; return nativeWidget.selectedIndex; } window.addEventListener("load", () =&gt; { const form = document.querySelector("form"); form.classList.remove("no-widget"); form.classList.add("widget"); }); window.addEventListener("load", () =&gt; { const selectList = document.querySelectorAll(".select"); selectList.forEach((select) =&gt; { const optionList = select.querySelectorAll(".option"); const selectedIndex = getIndex(select); select.tabIndex = 0; select.previousElementSibling.tabIndex = -1; updateValue(select, selectedIndex); optionList.forEach((option, index) =&gt; { option.addEventListener("mouseover", () =&gt; { highlightOption(select, option); }); option.addEventListener("click", (event) =&gt; { updateValue(select, index); }); }); select.addEventListener("click", (event) =&gt; { toggleOptList(select); }); select.addEventListener("focus", (event) =&gt; { activeSelect(select, selectList); }); select.addEventListener("blur", (event) =&gt; { deactivateSelect(select); }); select.addEventListener("keyup", (event) =&gt; { let index = getIndex(select); if (event.keyCode === 27) { deactivateSelect(select); } if (event.keyCode === 40 &amp;&amp; index &lt; optionList.length - 1) { index++; } if (event.keyCode === 38 &amp;&amp; index &gt; 0) { index--; } updateValue(select, index); }); }); }); </code></pre></div> <figure class="table-container"><table> <thead> <tr> <th>Live example</th> </tr> </thead> <tbody> <tr> <td>must be provided</td> </tr> </tbody> </table></figure> <pre class="notranslate"> | </pre> <p>| <a href="/en-US/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls/Example_5" class="only-in-en-us">Check out the final source code</a> |</p></div></section><section aria-labelledby="conclusion"><h2 id="conclusion"><a href="#conclusion">Conclusion</a></h2><div class="section-content"><p>We have seen all the basics of building a custom form widget, but as you can see it's not trivial to do, and often it's better and easier to rely on third-party libraries instead of coding them from scratch yourself (unless, of course, your goal is to build such a library).</p> <p>Here are a few libraries you should consider before coding your own:</p> <ul> <li><a href="https://jqueryui.com/" class="external" target="_blank">jQuery UI</a></li> <li><a href="https://github.com/marghoobsuleman/ms-Dropdown" class="external" target="_blank">msDropDown</a></li> <li><a href="https://www.emblematiq.com/lab/niceforms/" class="external" target="_blank">Nice Forms</a></li> <li><a href="https://www.google.fr/search?q=HTML+custom+form+controls&amp;ie=utf-8&amp;oe=utf-8&amp;aq=t&amp;rls=org.mozilla:fr:official&amp;client=firefox-a" class="external" target="_blank">And many more…</a></li> </ul> <p>If you want to move forward, the code in this example needs some improvement before it becomes generic and reusable. This is an exercise you can try to perform. Two hints to help you in this: the first argument for all our functions is the same, which means those functions need the same context. Building an object to share that context would be wise. Also, you need to make it feature-proof; that is, it needs to be able to work better with a variety of browsers whose compatibility with the Web standards they use vary. Have fun!</p> <p>page(Doc) not found /es/docs/Learn/HTML/Forms/Form_validation</p></div></section></article><aside class="article-footer"><div class="article-footer-inner"><div class="svg-container"><svg xmlns="http://www.w3.org/2000/svg" width="162" height="162" viewBox="0 0 162 162" fill="none" role="none"><mask id="b" fill="#fff"><path d="M97.203 47.04c8.113-7.886 18.004-13.871 28.906-17.492a78 78 0 0 1 33.969-3.39c11.443 1.39 22.401 5.295 32.024 11.411s17.656 14.28 23.476 23.86c5.819 9.579 9.269 20.318 10.083 31.385a69.85 69.85 0 0 1-5.387 32.44c-4.358 10.272-11.115 19.443-19.747 26.801-8.632 7.359-18.908 12.709-30.034 15.637l-6.17-21.698c7.666-2.017 14.746-5.703 20.694-10.773 5.948-5.071 10.603-11.389 13.606-18.467a48.14 48.14 0 0 0 3.712-22.352c-.561-7.625-2.938-15.025-6.948-21.625s-9.544-12.226-16.175-16.44-14.181-6.904-22.065-7.863a53.75 53.75 0 0 0-23.405 2.336c-7.513 2.495-14.327 6.62-19.918 12.053z"></path></mask><path stroke="url(#a)" stroke-dasharray="6, 6" stroke-width="2" d="M97.203 47.04c8.113-7.886 18.004-13.871 28.906-17.492a78 78 0 0 1 33.969-3.39c11.443 1.39 22.401 5.295 32.024 11.411s17.656 14.28 23.476 23.86c5.819 9.579 9.269 20.318 10.083 31.385a69.85 69.85 0 0 1-5.387 32.44c-4.358 10.272-11.115 19.443-19.747 26.801-8.632 7.359-18.908 12.709-30.034 15.637l-6.17-21.698c7.666-2.017 14.746-5.703 20.694-10.773 5.948-5.071 10.603-11.389 13.606-18.467a48.14 48.14 0 0 0 3.712-22.352c-.561-7.625-2.938-15.025-6.948-21.625s-9.544-12.226-16.175-16.44-14.181-6.904-22.065-7.863a53.75 53.75 0 0 0-23.405 2.336c-7.513 2.495-14.327 6.62-19.918 12.053z" mask="url(#b)" style="stroke:url(#a)" transform="translate(-63.992 -25.587)"></path><ellipse cx="8.066" cy="111.597" fill="var(--background-tertiary)" rx="53.677" ry="53.699" transform="matrix(.71707 -.697 .7243 .6895 0 0)"></ellipse><g clip-path="url(#c)" transform="translate(-63.992 -25.587)"><path fill="#9abff5" d="m144.256 137.379 32.906 12.434a4.41 4.41 0 0 1 2.559 5.667l-9.326 24.679a4.41 4.41 0 0 1-5.667 2.559l-8.226-3.108-2.332 6.17c-.466 1.233-.375 1.883-1.609 1.417l-2.253-.527c-.411-.155-.95-.594-1.206-1.161l-4.734-10.484-12.545-4.741a4.41 4.41 0 0 1-2.559-5.667l9.325-24.679a4.41 4.41 0 0 1 5.667-2.559m9.961 29.617 8.227 3.108 3.264-8.638-.498-6.768-4.113-1.555.548 7.258-4.319-1.632zm-12.339-4.663 8.226 3.108 3.264-8.637-.498-6.769-4.113-1.554.548 7.257-4.319-1.632z"></path></g><g clip-path="url(#d)" transform="translate(-63.992 -25.587)"><path fill="#81b0f3" d="M135.35 60.136 86.67 41.654c-3.346-1.27-7.124.428-8.394 3.775L64.414 81.938c-1.27 3.347.428 7.125 3.774 8.395l12.17 4.62-3.465 9.128c-.693 1.826-1.432 2.457.394 3.15l3.014 1.625c.609.231 1.637.274 2.477-.104l15.53-6.983 18.56 7.047c3.346 1.27 7.124-.428 8.395-3.775l13.862-36.51c1.27-3.346-.428-7.124-3.775-8.395M95.261 83.207l-12.17-4.62 4.852-12.779 7.19-7.017 6.085 2.31-7.725 7.51 6.389 2.426zm18.255 6.93-12.17-4.62 4.852-12.778 7.189-7.017 6.085 2.31-7.725 7.51 6.39 2.426z"></path></g><defs><clipPath id="c"><path fill="#fff" d="m198.638 146.586-65.056-24.583-24.583 65.057 65.056 24.582z"></path></clipPath><clipPath id="d"><path fill="#fff" d="m66.438 14.055 96.242 36.54-36.54 96.243-96.243-36.54z"></path></clipPath><linearGradient id="a" x1="97.203" x2="199.995" y1="47.04" y2="152.793" gradientUnits="userSpaceOnUse"><stop stop-color="#086DFC"></stop><stop offset="0.246" stop-color="#2C81FA"></stop><stop offset="0.516" stop-color="#5497F8"></stop><stop offset="0.821" stop-color="#80B0F6"></stop><stop offset="1" stop-color="#9ABFF5"></stop></linearGradient></defs></svg></div><h2>Help improve MDN</h2><fieldset class="feedback"><label>Was this page helpful to you?</label><div class="button-container"><button type="button" class="button primary has-icon yes"><span class="button-wrap"><span class="icon icon-thumbs-up "></span>Yes</span></button><button type="button" class="button primary has-icon no"><span class="button-wrap"><span class="icon icon-thumbs-down "></span>No</span></button></div></fieldset><a class="contribute" href="https://github.com/mdn/translated-content/blob/main/CONTRIBUTING.md" title="This will take you to our contribution guidelines on GitHub." target="_blank" rel="noopener noreferrer">Learn how to contribute</a>.<p class="last-modified-date">This page was last modified on<!-- --> <time dateTime="2024-12-21T04:32:59.000Z">21 dic 2024</time> by<!-- --> <a href="/es/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls/contributors.txt" rel="nofollow">MDN contributors</a>.</p><div id="on-github" class="on-github"><a href="https://github.com/mdn/translated-content/blob/main/files/es/learn_web_development/extensions/forms/how_to_build_custom_form_controls/index.md?plain=1" title="Folder: es/learn_web_development/extensions/forms/how_to_build_custom_form_controls (Opens in a new tab)" target="_blank" rel="noopener noreferrer">View this page on GitHub</a> <!-- -->•<!-- --> <a href="https://github.com/mdn/translated-content/issues/new?template=page-report-es.yml&amp;mdn-url=https%3A%2F%2Fdeveloper.mozilla.org%2Fes%2Fdocs%2FLearn_web_development%2FExtensions%2FForms%2FHow_to_build_custom_form_controls&amp;metadata=%3C%21--+Do+not+make+changes+below+this+line+--%3E%0A%3Cdetails%3E%0A%3Csummary%3EPage+report+details%3C%2Fsummary%3E%0A%0A*+Folder%3A+%60es%2Flearn_web_development%2Fextensions%2Fforms%2Fhow_to_build_custom_form_controls%60%0A*+MDN+URL%3A+https%3A%2F%2Fdeveloper.mozilla.org%2Fes%2Fdocs%2FLearn_web_development%2FExtensions%2FForms%2FHow_to_build_custom_form_controls%0A*+GitHub+URL%3A+https%3A%2F%2Fgithub.com%2Fmdn%2Ftranslated-content%2Fblob%2Fmain%2Ffiles%2Fes%2Flearn_web_development%2Fextensions%2Fforms%2Fhow_to_build_custom_form_controls%2Findex.md%0A*+Last+commit%3A+https%3A%2F%2Fgithub.com%2Fmdn%2Ftranslated-content%2Fcommit%2F82f53f6ffe2654ab442fb8d4ceaee4211ca90f7f%0A*+Document+last+modified%3A+2024-12-21T04%3A32%3A59.000Z%0A%0A%3C%2Fdetails%3E" title="This will take you to GitHub to file a new issue." target="_blank" rel="noopener noreferrer">Report a problem with this content</a></div></div></aside></main></div></div><footer id="nav-footer" class="page-footer"><div class="page-footer-grid"><div class="page-footer-logo-col"><a href="/" class="mdn-footer-logo" aria-label="MDN homepage"><svg width="48" height="17" viewBox="0 0 48 17" fill="none" xmlns="http://www.w3.org/2000/svg"><title id="mdn-footer-logo-svg">MDN logo</title><path d="M20.04 16.512H15.504V10.416C15.504 9.488 15.344 8.824 15.024 8.424C14.72 8.024 14.264 7.824 13.656 7.824C12.92 7.824 12.384 8.064 12.048 8.544C11.728 9.024 11.568 9.64 11.568 10.392V14.184H13.008V16.512H8.472V10.416C8.472 9.488 8.312 8.824 7.992 8.424C7.688 8.024 7.232 7.824 6.624 7.824C5.872 7.824 5.336 8.064 5.016 8.544C4.696 9.024 4.536 9.64 4.536 10.392V14.184H6.6V16.512H0V14.184H1.44V8.04H0.024V5.688H4.536V7.32C5.224 6.088 6.32 5.472 7.824 5.472C8.608 5.472 9.328 5.664 9.984 6.048C10.64 6.432 11.096 7.016 11.352 7.8C11.992 6.248 13.168 5.472 14.88 5.472C15.856 5.472 16.72 5.776 17.472 6.384C18.224 6.992 18.6 7.936 18.6 9.216V14.184H20.04V16.512Z" fill="currentColor"></path><path d="M33.6714 16.512H29.1354V14.496C28.8314 15.12 28.3834 15.656 27.7914 16.104C27.1994 16.536 26.4154 16.752 25.4394 16.752C24.0154 16.752 22.8954 16.264 22.0794 15.288C21.2634 14.312 20.8554 12.984 20.8554 11.304C20.8554 9.688 21.2554 8.312 22.0554 7.176C22.8554 6.04 24.0634 5.472 25.6794 5.472C26.5594 5.472 27.2794 5.648 27.8394 6C28.3994 6.352 28.8314 6.8 29.1354 7.344V2.352H26.9754V0H32.2314V14.184H33.6714V16.512ZM29.1354 11.04V10.776C29.1354 9.88 28.8954 9.184 28.4154 8.688C27.9514 8.176 27.3674 7.92 26.6634 7.92C25.9754 7.92 25.3674 8.176 24.8394 8.688C24.3274 9.2 24.0714 10.008 24.0714 11.112C24.0714 12.152 24.3114 12.944 24.7914 13.488C25.2714 14.032 25.8394 14.304 26.4954 14.304C27.3114 14.304 27.9514 13.96 28.4154 13.272C28.8954 12.584 29.1354 11.84 29.1354 11.04Z" fill="currentColor"></path><path d="M47.9589 16.512H41.9829V14.184H43.4229V10.416C43.4229 9.488 43.2629 8.824 42.9429 8.424C42.6389 8.024 42.1829 7.824 41.5749 7.824C40.8389 7.824 40.2709 8.056 39.8709 8.52C39.4709 8.968 39.2629 9.56 39.2469 10.296V14.184H40.6869V16.512H34.7109V14.184H36.1509V8.04H34.5909V5.688H39.2469V7.344C39.9669 6.096 41.1269 5.472 42.7269 5.472C43.7509 5.472 44.6389 5.776 45.3909 6.384C46.1429 6.992 46.5189 7.936 46.5189 9.216V14.184H47.9589V16.512Z" fill="currentColor"></path></svg></a><p>Your blueprint for a better internet.</p><ul class="social-icons"><li><a href="https://mastodon.social/@mdn" target="_blank" rel="me noopener noreferrer"><span class="icon icon-mastodon"></span><span class="visually-hidden">MDN on Mastodon</span></a></li><li><a href="https://twitter.com/mozdevnet" target="_blank" rel="noopener noreferrer"><span class="icon icon-twitter-x"></span><span class="visually-hidden">MDN on X (formerly Twitter)</span></a></li><li><a href="https://github.com/mdn/" target="_blank" rel="noopener noreferrer"><span class="icon icon-github-mark-small"></span><span class="visually-hidden">MDN on GitHub</span></a></li><li><a href="/en-US/blog/rss.xml" target="_blank"><span class="icon icon-feed"></span><span class="visually-hidden">MDN Blog RSS Feed</span></a></li></ul></div><div class="page-footer-nav-col-1"><h2 class="footer-nav-heading">MDN</h2><ul class="footer-nav-list"><li class="footer-nav-item"><a href="/en-US/about">About</a></li><li class="footer-nav-item"><a href="/en-US/blog/">Blog</a></li><li class="footer-nav-item"><a href="https://www.mozilla.org/en-US/careers/listings/?team=ProdOps" target="_blank" rel="noopener noreferrer">Careers</a></li><li class="footer-nav-item"><a href="/en-US/advertising">Advertise with us</a></li></ul></div><div class="page-footer-nav-col-2"><h2 class="footer-nav-heading">Support</h2><ul class="footer-nav-list"><li class="footer-nav-item"><a class="footer-nav-link" href="https://support.mozilla.org/products/mdn-plus">Product help</a></li><li class="footer-nav-item"><a class="footer-nav-link" href="/es/docs/MDN/Community/Issues">Report an issue</a></li></ul></div><div class="page-footer-nav-col-3"><h2 class="footer-nav-heading">Our communities</h2><ul class="footer-nav-list"><li class="footer-nav-item"><a class="footer-nav-link" href="/en-US/community">MDN Community</a></li><li class="footer-nav-item"><a class="footer-nav-link" href="https://discourse.mozilla.org/c/mdn/236" target="_blank" rel="noopener noreferrer">MDN Forum</a></li><li class="footer-nav-item"><a class="footer-nav-link" href="/discord" target="_blank" rel="noopener noreferrer">MDN Chat</a></li></ul></div><div class="page-footer-nav-col-4"><h2 class="footer-nav-heading">Developers</h2><ul class="footer-nav-list"><li class="footer-nav-item"><a class="footer-nav-link" href="/es/docs/Web">Web Technologies</a></li><li class="footer-nav-item"><a class="footer-nav-link" href="/es/docs/Learn">Learn Web Development</a></li><li class="footer-nav-item"><a class="footer-nav-link" href="/es/plus">MDN Plus</a></li><li class="footer-nav-item"><a href="https://hacks.mozilla.org/" target="_blank" rel="noopener noreferrer">Hacks Blog</a></li></ul></div><div class="page-footer-moz"><a href="https://www.mozilla.org/" class="footer-moz-logo-link" target="_blank" rel="noopener noreferrer"><svg xmlns="http://www.w3.org/2000/svg" width="137" height="32" fill="none" viewBox="0 0 267.431 62.607"><path fill="currentColor" d="m13.913 23.056 5.33 25.356h2.195l5.33-25.356h14.267v38.976h-7.578V29.694h-2.194l-7.264 32.337h-7.343L9.418 29.694H7.223v32.337H-.354V23.056Zm47.137 9.123c9.12 0 14.423 5.385 14.423 15.214s-5.33 15.214-14.423 15.214c-9.12 0-14.423-5.385-14.423-15.214 0-9.855 5.304-15.214 14.423-15.214m0 24.363c4.285 0 6.428-2.196 6.428-7.032v-4.287c0-4.836-2.143-7.032-6.428-7.032s-6.428 2.196-6.428 7.032v4.287c0 4.836 2.143 7.032 6.428 7.032m18.473-.157 15.47-18.01h-15.26v-5.647h24.352v5.646L88.616 56.385h15.704v5.646H79.523Zm29.318-23.657h11.183V62.03h-7.578V38.375h-3.632v-5.646zm3.605-9.672h7.578v5.646h-7.578zm13.17 0h11.21v38.976h-7.578v-33.33h-3.632zm16.801 0H153.6v38.976h-7.577v-33.33h-3.632v-5.646zm29.03 9.123c4.442 0 7.394 2.143 8.231 5.881h2.194v-5.332h9.276v5.646h-3.632v18.011h3.632v5.646h-4.442c-3.135 0-4.834-1.699-4.834-4.836V56.7h-2.194c-.81 3.738-3.789 5.881-8.23 5.881-6.978 0-11.916-5.829-11.916-15.214 0-9.384 4.938-15.187 11.915-15.187m2.3 24.363c4.284 0 6.192-2.196 6.192-7.032v-4.287c0-4.836-1.908-7.032-6.193-7.032-4.18 0-6.193 2.196-6.193 7.032v4.287c0 4.836 2.012 7.032 6.193 7.032m48.34 5.489h-7.577V0h7.577zm6.585-29.643h32.165v-2.196l-21.295-7.634v-6.143l21.295-7.633V6.588h-25.345V0h32.165v12.522l-17.35 5.881V20.6l17.35 5.882v12.521h-38.985zm0-25.801h6.794v6.796h-6.794z"></path></svg></a><ul class="footer-moz-list"><li class="footer-moz-item"><a href="https://www.mozilla.org/privacy/websites/" class="footer-moz-link" target="_blank" rel="noopener noreferrer">Website Privacy Notice</a></li><li class="footer-moz-item"><a href="https://www.mozilla.org/privacy/websites/#cookies" class="footer-moz-link" target="_blank" rel="noopener noreferrer">Cookies</a></li><li class="footer-moz-item"><a href="https://www.mozilla.org/about/legal/terms/mozilla" class="footer-moz-link" target="_blank" rel="noopener noreferrer">Legal</a></li><li class="footer-moz-item"><a href="https://www.mozilla.org/about/governance/policies/participation/" class="footer-moz-link" target="_blank" rel="noopener noreferrer">Community Participation Guidelines</a></li></ul></div><div class="page-footer-legal"><p id="license" class="page-footer-legal-text">Visit<!-- --> <a href="https://www.mozilla.org" target="_blank" rel="noopener noreferrer">Mozilla Corporation’s</a> <!-- -->not-for-profit parent, the<!-- --> <a target="_blank" rel="noopener noreferrer" href="https://foundation.mozilla.org/">Mozilla Foundation</a>.<br/>Portions of this content are ©1998–<!-- -->2025<!-- --> by individual mozilla.org contributors. Content available under<!-- --> <a href="/es/docs/MDN/Writing_guidelines/Attrib_copyright_license">a Creative Commons license</a>.</p></div></div></footer></div><script type="application/json" id="hydration">{"url":"/es/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls","doc":{"body":[{"type":"prose","value":{"id":null,"title":null,"isH3":false,"content":"<p>page(Doc) not found /es/docs/Learn/HTML/Forms/Form_validation</p>\n<p>Hay muchos casos donde los <a href=\"/es/docs/Learn_web_development/Extensions/Forms/Basic_native_form_controls\">widgets de formularios HTML disponibles</a> simplemente no son suficientes. si desea <a href=\"/en-US/docs/Learn_web_development/Extensions/Forms/Advanced_form_styling\" class=\"only-in-en-us\">establecer un estilo avanzado</a> en algunos widgets como el elemento <a href=\"/es/docs/Web/HTML/Element/select\"><code>&lt;select&gt;</code></a> o si desea proporcionar comportamientos personalizados, no tiene más opción que crear sus propios widgets.</p>\n<p>En este aartículo, veremos cómo construir dicho widget. Para ello, trabajaremos con un ejemplo: Reconstruir el elemento <a href=\"/es/docs/Web/HTML/Element/select\"><code>&lt;select&gt;</code></a>.</p>\n<div class=\"notecard note\">\n<p><strong>Nota:</strong>\nNos enfocaremos en construir los widgets, no en cómo hacer que el código sea genérico y reutilizable; eso implicaría algún código JavaScript no trivial y manipulación del DOM en un contexto desconocido, y eso está fuera del alcance de este artículo.</p>\n</div>"}},{"type":"prose","value":{"id":"diseño_estructura_y_semántica","title":"Diseño, estructura, y semántica","isH3":false,"content":"<p>Antes de crear un widget personalizado, debería iniciar por averiguar exactamente qué es lo que desea. Esto le ahorarrá tiempo considerable. En particular, es importante definir claramente todos los estados de su widget. Para hacer esto, es bueno comenzar con un widget existente, cuyos estados y comportamientos son bien conocidos, por lo que simplemente puede imitarlos tanto como sea posible.</p>\n<p>En nuestro ejemplo, reconstruiremos el elemento <a href=\"/es/docs/Web/HTML/Element/select\"><code>&lt;select&gt;</code></a>. Este es el resultado que queremos lograr:</p>\n<p><img src=\"/files/4481/custom-select.png\" alt=\"The three states of a select box\" loading=\"lazy\"></p>\n<p>Esta captura de pantall muestra los tres estados principales de nuestro widget: el estado normal (a la izquiera); el estado activo (en el centro) y el estado abierto (a la derecha).</p>\n<p>En términos de comportamiento, queremos que nuestro widget sea utilizable tanto con un ratón como con un teclado, al igual que cualquier widget nativo. Comencemos por definir cómo el widget llega a cada estado:</p>\n<p>El widget está en su estado normal cuando:</p>\n<ul>\n<li>La página carga</li>\n<li>El widget estaba activo y el usuario hace clic en cualquier lugar fuera del widget</li>\n<li>El widget estaba activo y el usuario mueve el foco a otro widget usando el teclado</li>\n</ul>\n<div class=\"notecard note\">\n<p><strong>Nota:</strong>\nMover el foco al rededor de la página generalmente se hace presionando la tecla de tabulación, pero este no es el estándar en todas partes. Por ejemplo, el ciclo a través de enlaces en una página se realiza en Safari de forma predeterminada usando la combinación <a href=\"https://www.456bereastreet.com/archive/200906/enabling_keyboard_navigation_in_mac_os_x_web_browsers/\" class=\"external\" target=\"_blank\">combinación Opction+Tab</a>.</p>\n</div>\n<p>El widget está en su estado activo cuando:</p>\n<ul>\n<li>El usuario hace clic en él</li>\n<li>El usuario presiona la tecla tab y obtiene foco</li>\n<li>El widget estaba en su estado abierto y el usuario hace clic en el widget.</li>\n</ul>\n<p>El widget está en su estado abierto cuando:</p>\n<ul>\n<li>El widget está en cualquier otro estado diferente a abierto y el usuario hace clic en él.</li>\n</ul>\n<p>Una vez que sabemos cómo cambiar los estados, es importante definir cómo cambiar el valor del widget:</p>\n<p>El valor cambia cuando:</p>\n<ul>\n<li>El usuario hace clic en una opción cuando el widget está en estado abierto</li>\n<li>El usuario pulsa las teclas de flecha hacia arriba o hacia abajocuando el widget está en estado activo</li>\n</ul>\n<p>Finalmente, definamos cómo se comportarán las opciones del widget:</p>\n<ul>\n<li>Cuando se abre el widget, se resalta la opción seleccionada</li>\n<li>Cuando el ratón está sobre una opción, la opción se resalta y la opción resaltada anteriormente vuelve a su estado normal</li>\n</ul>\n<p>Para los fines de nuestro ejemplo, nos detendremos con eso; sin embargo, si eres un lector cuidadoso, notarás que faltan algunos comportamientos. Por ejemplo, ¿qué crees que sucederá si el usuario pulsa la tecla de tabulación mientras el widget está en estado abierto? La respuesta es ... nada. OK, el comportamiento correcto parece obvio, pero el hecho es que, como no está definido en nuestras especificaciones, es muy fácil pasar por alto este comportamiento. Esto es especialmente cierto en un entorno de equipo cuando las personas que diseñan el comportamiento del widget son diferentes de las que lo implementan.</p>\n<p>Otro ejemplo divertido: ¿qué pasará si el usuario pulsa las teclas de flecha hacia arriba o hacia abajo mientras el widget está en estado abierto? Este es un poco más complicado. Si considera que el estado activo y el estado abierto son completamente diferentes, la respuesta es nuevamente \"no pasará nada\" porque no definimos ninguna interacción de teclado para el estado abierto. Por otro lado, si considera que el estado activo y el estado abierto se superponen un poco, el valor puede cambiar pero la opción definitivamente no se resaltará en consecuencia, una vez más porque no definimos ninguna interacción del teclado sobre las opciones cuando el widget es en su estado abierto (solo hemos definido lo que debería suceder cuando se abre el widget, pero nada después de eso).</p>\n<p>En nuestro ejemplo, las especificaciones faltantes son obvias, así que las manejaremos, pero puede ser un problema real en widgets nuevos y exóticos, para los cuales nadie tiene la menor idea de cuál es el comportamiento correcto. Por lo tanto, siempre es bueno pasar tiempo en esta etapa de diseño, porque si defines un comportamiento deficiente u olvidas definir uno, será muy difícil redefinirlo una vez que los usuarios se hayan acostumbrado. Si tiene dudas, solicite las opiniones de los demás y, si tiene el presupuesto para ello, no dude en realizar las pruebas de usuario. Este proceso se llama Diseño UX. Si desea obtener más información sobre este tema, debe consultar los siguientes recursos útiles:</p>\n<ul>\n<li><a href=\"https://www.uxmatters.com/\" class=\"external\" target=\"_blank\">UXMatters.com</a></li>\n<li><a href=\"https://uxdesign.com/\" class=\"external\" target=\"_blank\">UXDesign.com</a></li>\n<li><a href=\"https://uxdesign.smashingmagazine.com/\" class=\"external\" target=\"_blank\">The UX Design section of SmashingMagazine</a></li>\n</ul>\n<div class=\"notecard note\">\n<p><strong>Nota:</strong>\nAdemas, en la mayoría de los sistemas hay una forma de abrir el elemento <a href=\"/es/docs/Web/HTML/Element/select\"><code>&lt;select&gt;</code></a> para ver todas las opciones disponibles (esto es lo mismo que hacer clic en el elemento <a href=\"/es/docs/Web/HTML/Element/select\"><code>&lt;select&gt;</code></a> con un ratón). Esto se logra con Alt+Flecha abajo en Windows y no fué implementado en nuestro ejemplo —pero sería facil hacerlo, ya que el mecanismo ya se implementó para el evento <code>clic</code>.</p>\n</div>"}},{"type":"prose","value":{"id":"definiendo_la_estructura_y_semántica_html","title":"Definiendo la estructura y semántica HTML","isH3":true,"content":"<p>Ahora que se ha decidido la funcionalidad básica del widget, es hora de comenzar a construir nuestro widget. El primer paso es definir su estructura HTML y darle una semántica básica. Esto es lo que necesitamos para reconstruir un elemento <a href=\"/es/docs/Web/HTML/Element/select\"><code>&lt;select&gt;</code></a>:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">html</span></div><pre class=\"brush: html notranslate\"><code>&lt;!-- Este es nuestro contenedor principal para nuestro widget.\n El atributo tabindex es lo que permite al usuario enforcar el widget.\n Veremos más adelante que es mejor configurarlo a través de JavaScript. --&gt;\n&lt;div class=\"select\" tabindex=\"0\"&gt;\n &lt;!-- Este contenedor será usado para mostrar el valor actual del widget --&gt;\n &lt;span class=\"value\"&gt;Cherry&lt;/span&gt;\n\n &lt;!-- Este contenedor contedrá todas las opciones disponibles para nuestro widget.\n Como es una lista, tiene sentido usar el elemento ul. --&gt;\n &lt;ul class=\"optList\"&gt;\n &lt;!-- Cada opción solo contiene el valor que se mostrará, veremos más tarde\n cómo manejar el valor real que será enviado con el formulario de datos --&gt;\n &lt;li class=\"option\"&gt;Cherry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Lemon&lt;/li&gt;\n &lt;li class=\"option\"&gt;Banana&lt;/li&gt;\n &lt;li class=\"option\"&gt;Strawberry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Apple&lt;/li&gt;\n &lt;/ul&gt;\n&lt;/div&gt;\n</code></pre></div>\n<p>Tenga en cuanta el uso de nombres de clases; estos identifican cada parte relevante independientemente de los elementos HTML subyacentes reales utilizados. Esto es importante para garantizar que no vinculamos nuestro CSS y JavaScript a una estructura HTML sólida, de modo que podamos realizar cambios despues en la implementación sin romper el código que usa el widget. Pro ejemplo, si desea implementar el equivalente del elemento <a href=\"/en-US/docs/Web/HTML/Element/optgroup\" class=\"only-in-en-us\"><code>&lt;optgroup&gt;</code></a>.</p>"}},{"type":"prose","value":{"id":"creating_the_look_and_feel_using_css","title":"Creating the look and feel using CSS","isH3":true,"content":"<p>Now that we have a structure, we can start designing our widget. The whole point of building this custom widget is to be able to style this widget exactly as we want. To that end, we will split our CSS work into two parts: the first part will be the CSS rules absolutely necessary to have our widget behave like a <a href=\"/es/docs/Web/HTML/Element/select\"><code>&lt;select&gt;</code></a> element, and the second part will consist of the fancy styles used to make it look the way we want.</p>\n<h4 id=\"required_styles\">Required styles</h4>\n<p>The required styles are those necessary to handle the three states of our widget.</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">css</span></div><pre class=\"brush: css notranslate\"><code>.select {\n /* This will create a positioning context for the list of options */\n position: relative;\n\n /* This will make our widget become part of the text flow and sizable at the same time */\n display: inline-block;\n}\n</code></pre></div>\n<p>We need an extra class <code>active</code> to define the look and feel of our widget when it is in its active state. Because our widget is focusable, we double this custom style with the <a href=\"/es/docs/Web/CSS/:focus\"><code>:focus</code></a> pseudo-class in order to be sure they will behave the same.</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">css</span></div><pre class=\"brush: css notranslate\"><code>.select.active,\n.select:focus {\n outline: none;\n\n /* This box-shadow property is not exactly required, however it's so important to be sure\n the active state is visible that we use it as a default value, feel free to override it. */\n box-shadow: 0 0 3px 1px #227755;\n}\n</code></pre></div>\n<p>Now, let's handle the list of options:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">css</span></div><pre class=\"brush: css notranslate\"><code>/* The .select selector here is syntactic sugar to be sure the classes we define are\n the ones inside our widget. */\n.select .optList {\n /* This will make sure our list of options will be displayed below the value\n and out of the HTML flow */\n position: absolute;\n top: 100%;\n left: 0;\n}\n</code></pre></div>\n<p>We need an extra class to handle when the list of options is hidden. This is necessary in order to manage the differences between the active state and the open state that do not exactly match.</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">css</span></div><pre class=\"brush: css notranslate\"><code>.select .optList.hidden {\n /* This is a simple way to hide the list in an accessible way,\n we will talk more about accessibility in the end */\n max-height: 0;\n visibility: hidden;\n}\n</code></pre></div>\n<h4 id=\"beautification\">Beautification</h4>\n<p>So now that we have the basic functionality in place, the fun can start. The following is just an example of what is possible, and will match the screenshot at the beginning of this article. However, you should feel free to experiment and see what you can come up with.</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">css</span></div><pre class=\"brush: css notranslate\"><code>.select {\n /* All sizes will be expressed with the em value for accessibility reasons\n (to make sure the widget remains resizable if the user uses the\n browser's zoom in a text-only mode). The computations are made\n assuming 1em == 16px which is the default value in most browsers.\n If you are lost with px to em conversion, try https://riddle.pl/emcalc/ */\n font-size: 0.625em; /* this (10px) is the new font size context for em value in this context */\n font-family: Verdana, Arial, sans-serif;\n\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n\n /* We need extra room for the down arrow we will add */\n padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */\n width: 10em; /* 100px */\n\n border: 0.2em solid #000; /* 2px */\n border-radius: 0.4em; /* 4px */\n box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */\n\n /* The first declaration is for browsers that do not support linear gradients.\n The second declaration is because WebKit based browsers haven't unprefixed it yet.\n If you want to support legacy browsers, try https://www.colorzilla.com/gradient-editor/ */\n background: #f0f0f0;\n background: -webkit-linear-gradient(90deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);\n background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);\n}\n\n.select .value {\n /* Because the value can be wider than our widget, we have to make sure it will not\n change the widget's width */\n display: inline-block;\n width: 100%;\n overflow: hidden;\n\n vertical-align: top;\n\n /* And if the content overflows, it's better to have a nice ellipsis. */\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n</code></pre></div>\n<p>We don't need an extra element to design the down arrow; instead, we're using the <a href=\"/es/docs/Web/CSS/::after\"><code>::after</code></a> pseudo-element. However, it could also be implemented using a simple background image on the <code>select</code> class.</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">css</span></div><pre class=\"brush: css notranslate\"><code>.select:after {\n content: \"▼\"; /* We use the unicode caracter U+25BC; see https://www.utf8-chartable.de */\n position: absolute;\n z-index: 1; /* This will be important to keep the arrow from overlapping the list of options */\n top: 0;\n right: 0;\n\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n\n height: 100%;\n width: 2em; /* 20px */\n padding-top: 0.1em; /* 1px */\n\n border-left: 0.2em solid #000; /* 2px */\n border-radius: 0 0.1em 0.1em 0; /* 0 1px 1px 0 */\n\n background-color: #000;\n color: #fff;\n text-align: center;\n}\n</code></pre></div>\n<p>Next, let's style the list of options:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">css</span></div><pre class=\"brush: css notranslate\"><code>.select .optList {\n z-index: 2; /* We explicitly said the list of options will always overlap the down arrow */\n\n /* this will reset the default style of the ul element */\n list-style: none;\n margin: 0;\n padding: 0;\n\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n\n /* This will ensure that even if the values are smaller than the widget,\n the list of options will be as large as the widget itself */\n min-width: 100%;\n\n /* In case the list is too long, its content will overflow vertically\n (which will add a vertical scrollbar automatically) but never horizontally\n (because we haven't set a width, the list will adjust its width automatically.\n If it can't, the content will be truncated) */\n max-height: 10em; /* 100px */\n overflow-y: auto;\n overflow-x: hidden;\n\n border: 0.2em solid #000; /* 2px */\n border-top-width: 0.1em; /* 1px */\n border-radius: 0 0 0.4em 0.4em; /* 0 0 4px 4px */\n\n box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4); /* 0 2px 4px */\n background: #f0f0f0;\n}\n</code></pre></div>\n<p>For the options, we need to add a <code>highlight</code> class to be able to identify the value the user will pick (or has picked).</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">css</span></div><pre class=\"brush: css notranslate\"><code>.select .option {\n padding: 0.2em 0.3em; /* 2px 3px */\n}\n\n.select .highlight {\n background: #000;\n color: #ffffff;\n}\n</code></pre></div>\n<p>So here's the result with our three states:</p>\n<h4 id=\"basic_state\">Basic state</h4>\n<div class=\"code-example\"><pre class=\"brush: html hidden notranslate\"><code>&lt;div class=\"select\"&gt;\n &lt;span class=\"value\"&gt;Cherry&lt;/span&gt;\n &lt;ul class=\"optList hidden\"&gt;\n &lt;li class=\"option\"&gt;Cherry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Lemon&lt;/li&gt;\n &lt;li class=\"option\"&gt;Banana&lt;/li&gt;\n &lt;li class=\"option\"&gt;Strawberry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Apple&lt;/li&gt;\n &lt;/ul&gt;\n&lt;/div&gt;\n</code></pre></div>\n<div class=\"code-example\"><pre class=\"brush: css hidden notranslate\"><code>.select {\n position: relative;\n display: inline-block;\n}\n\n.select.active,\n.select:focus {\n box-shadow: 0 0 3px 1px #227755;\n outline: none;\n}\n\n.select .optList {\n position: absolute;\n top: 100%;\n left: 0;\n}\n\n.select .optList.hidden {\n max-height: 0;\n visibility: hidden;\n}\n\n.select {\n font-size: 0.625em; /* 10px */\n font-family: Verdana, Arial, sans-serif;\n\n box-sizing: border-box;\n\n padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */\n width: 10em; /* 100px */\n\n border: 0.2em solid #000; /* 2px */\n border-radius: 0.4em; /* 4px */\n\n box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */\n\n background: #f0f0f0;\n background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);\n}\n\n.select .value {\n display: inline-block;\n width: 100%;\n overflow: hidden;\n\n white-space: nowrap;\n text-overflow: ellipsis;\n vertical-align: top;\n}\n\n.select:after {\n content: \"▼\";\n position: absolute;\n z-index: 1;\n height: 100%;\n width: 2em; /* 20px */\n top: 0;\n right: 0;\n\n padding-top: 0.1em;\n\n box-sizing: border-box;\n\n text-align: center;\n\n border-left: 0.2em solid #000;\n border-radius: 0 0.1em 0.1em 0;\n\n background-color: #000;\n color: #fff;\n}\n\n.select .optList {\n z-index: 2;\n\n list-style: none;\n margin: 0;\n padding: 0;\n\n background: #f0f0f0;\n border: 0.2em solid #000;\n border-top-width: 0.1em;\n border-radius: 0 0 0.4em 0.4em;\n\n box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);\n\n box-sizing: border-box;\n\n min-width: 100%;\n max-height: 10em; /* 100px */\n overflow-y: auto;\n overflow-x: hidden;\n}\n\n.select .option {\n padding: 0.2em 0.3em;\n}\n\n.select .highlight {\n background: #000;\n color: #ffffff;\n}\n</code></pre></div>\n<p>must be provided</p>\n<h4 id=\"active_state\">Active state</h4>\n<div class=\"code-example\"><pre class=\"brush: html hidden notranslate\"><code>&lt;div class=\"select active\"&gt;\n &lt;span class=\"value\"&gt;Cherry&lt;/span&gt;\n &lt;ul class=\"optList hidden\"&gt;\n &lt;li class=\"option\"&gt;Cherry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Lemon&lt;/li&gt;\n &lt;li class=\"option\"&gt;Banana&lt;/li&gt;\n &lt;li class=\"option\"&gt;Strawberry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Apple&lt;/li&gt;\n &lt;/ul&gt;\n&lt;/div&gt;\n</code></pre></div>\n<div class=\"code-example\"><pre class=\"brush: css hidden notranslate\"><code>.select {\n position: relative;\n display: inline-block;\n}\n\n.select.active,\n.select:focus {\n box-shadow: 0 0 3px 1px #227755;\n outline: none;\n}\n\n.select .optList {\n position: absolute;\n top: 100%;\n left: 0;\n}\n\n.select .optList.hidden {\n max-height: 0;\n visibility: hidden;\n}\n\n.select {\n font-size: 0.625em; /* 10px */\n font-family: Verdana, Arial, sans-serif;\n\n box-sizing: border-box;\n\n padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */\n width: 10em; /* 100px */\n\n border: 0.2em solid #000; /* 2px */\n border-radius: 0.4em; /* 4px */\n\n box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */\n\n background: #f0f0f0;\n background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);\n}\n\n.select .value {\n display: inline-block;\n width: 100%;\n overflow: hidden;\n\n white-space: nowrap;\n text-overflow: ellipsis;\n vertical-align: top;\n}\n\n.select:after {\n content: \"▼\";\n position: absolute;\n z-index: 1;\n height: 100%;\n width: 2em; /* 20px */\n top: 0;\n right: 0;\n\n padding-top: 0.1em;\n\n box-sizing: border-box;\n\n text-align: center;\n\n border-left: 0.2em solid #000;\n border-radius: 0 0.1em 0.1em 0;\n\n background-color: #000;\n color: #fff;\n}\n\n.select .optList {\n z-index: 2;\n\n list-style: none;\n margin: 0;\n padding: 0;\n\n background: #f0f0f0;\n border: 0.2em solid #000;\n border-top-width: 0.1em;\n border-radius: 0 0 0.4em 0.4em;\n\n box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);\n\n box-sizing: border-box;\n\n min-width: 100%;\n max-height: 10em; /* 100px */\n overflow-y: auto;\n overflow-x: hidden;\n}\n\n.select .option {\n padding: 0.2em 0.3em;\n}\n\n.select .highlight {\n background: #000;\n color: #ffffff;\n}\n</code></pre></div>\n<p>must be provided</p>\n<h4 id=\"open_state\">Open state</h4>\n<div class=\"code-example\"><pre class=\"brush: html hidden notranslate\"><code>&lt;div class=\"select active\"&gt;\n &lt;span class=\"value\"&gt;Cherry&lt;/span&gt;\n &lt;ul class=\"optList\"&gt;\n &lt;li class=\"option highlight\"&gt;Cherry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Lemon&lt;/li&gt;\n &lt;li class=\"option\"&gt;Banana&lt;/li&gt;\n &lt;li class=\"option\"&gt;Strawberry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Apple&lt;/li&gt;\n &lt;/ul&gt;\n&lt;/div&gt;\n</code></pre></div>\n<div class=\"code-example\"><pre class=\"brush: css hidden notranslate\"><code>.select {\n position: relative;\n display: inline-block;\n}\n\n.select.active,\n.select:focus {\n box-shadow: 0 0 3px 1px #227755;\n outline: none;\n}\n\n.select .optList {\n position: absolute;\n top: 100%;\n left: 0;\n}\n\n.select .optList.hidden {\n max-height: 0;\n visibility: hidden;\n}\n\n.select {\n font-size: 0.625em; /* 10px */\n font-family: Verdana, Arial, sans-serif;\n\n box-sizing: border-box;\n\n padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */\n width: 10em; /* 100px */\n\n border: 0.2em solid #000; /* 2px */\n border-radius: 0.4em; /* 4px */\n\n box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */\n\n background: #f0f0f0;\n background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);\n}\n\n.select .value {\n display: inline-block;\n width: 100%;\n overflow: hidden;\n\n white-space: nowrap;\n text-overflow: ellipsis;\n vertical-align: top;\n}\n\n.select:after {\n content: \"▼\";\n position: absolute;\n z-index: 1;\n height: 100%;\n width: 2em; /* 20px */\n top: 0;\n right: 0;\n\n padding-top: 0.1em;\n\n box-sizing: border-box;\n\n text-align: center;\n\n border-left: 0.2em solid #000;\n border-radius: 0 0.1em 0.1em 0;\n\n background-color: #000;\n color: #fff;\n}\n\n.select .optList {\n z-index: 2;\n\n list-style: none;\n margin: 0;\n padding: 0;\n\n background: #f0f0f0;\n border: 0.2em solid #000;\n border-top-width: 0.1em;\n border-radius: 0 0 0.4em 0.4em;\n\n box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);\n\n box-sizing: border-box;\n\n min-width: 100%;\n max-height: 10em; /* 100px */\n overflow-y: auto;\n overflow-x: hidden;\n}\n\n.select .option {\n padding: 0.2em 0.3em;\n}\n\n.select .highlight {\n background: #000;\n color: #fff;\n}\n</code></pre></div>\n<p>must be provided</p>"}},{"type":"prose","value":{"id":"bring_your_widget_to_life_with_javascript","title":"Bring your widget to life with JavaScript","isH3":false,"content":"<p>Now that our design and structure are ready, we can write the JavaScript code to make the widget actually work.</p>\n<div class=\"notecard warning\">\n<p><strong>Advertencia:</strong>\nThe following code is educational and should not be used as-is. Among many things, as we'll see, it is not future-proof and it will not work on legacy browsers. It also has redundant parts that should be optimized in production code.</p>\n</div>\n<div class=\"notecard note\">\n<p><strong>Nota:</strong>\nCreating reusable widgets is something that can be a bit tricky. The <a href=\"https://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html\" class=\"external\" target=\"_blank\">W3C Web Component draft</a> is one of the answers to this specific issue. <a href=\"http://x-tags.org/\" class=\"external\" target=\"_blank\">The X-Tag project</a> is a test implementation of this specification; we encourage you to take a look at it.</p>\n</div>"}},{"type":"prose","value":{"id":"why_isnt_it_working","title":"Why isn't it working?","isH3":true,"content":"<p>Before we start, it's important to remember something very important about JavaScript: inside a browser, <strong>it's an unreliable technology</strong>. When you are building custom widgets, you'll have to rely on JavaScript because it's a necessary thread to tie everything together. However, there are many cases in which JavaScript isn't able to run in the browser:</p>\n<ul>\n<li>The user has turned off JavaScript: This is the most unusual case ever; very few people turn off JavaScript nowadays.</li>\n<li>The script is not loading. This is one of the most common cases, especially in the mobile world where the network is not very reliable.</li>\n<li>The script is buggy. You should always consider this possibility.</li>\n<li>The script is in conflict with a third party script. This can happen with tracking scripts or any bookmarklets the user uses.</li>\n<li>The script is in conflict with, or is affected by, a browser extension (such as Firefox's <a href=\"https://addons.mozilla.org/fr/firefox/addon/noscript/\" class=\"external\" target=\"_blank\">NoScript</a> extension or Chrome's <a href=\"https://chrome.google.com/webstore/detail/notscripts/odjhifogjcknibkahlpidmdajjpkkcfn\" class=\"external\" target=\"_blank\">NotScripts</a> extension).</li>\n<li>The user is using a legacy browser, and one of the features you require is not supported. This will happen frequently when you make use of cutting-edge APIs.</li>\n</ul>\n<p>Because of these risks, it's really important to seriously consider what will happen if JavaScript isn't working. Dealing in detail with this issue is out of the scope of this article because it's closely linked to how you want to make your script generic and reusable, but we'll consider the basics of this in our example.</p>\n<p>In our example, if our JavaScript code isn't running, we'll fall back to displaying a standard <a href=\"/es/docs/Web/HTML/Element/select\"><code>&lt;select&gt;</code></a> element. To achieve this, we need two things.</p>\n<p>First, we need to add a regular <a href=\"/es/docs/Web/HTML/Element/select\"><code>&lt;select&gt;</code></a> element before each use of our custom widget. This is actually also required in order to be able to send data from our custom widget along with the rest of our form data; more about this later.</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">html</span></div><pre class=\"brush: html notranslate\"><code>&lt;body class=\"no-widget\"&gt;\n &lt;form&gt;\n &lt;select name=\"myFruit\"&gt;\n &lt;option&gt;Cherry&lt;/option&gt;\n &lt;option&gt;Lemon&lt;/option&gt;\n &lt;option&gt;Banana&lt;/option&gt;\n &lt;option&gt;Strawberry&lt;/option&gt;\n &lt;option&gt;Apple&lt;/option&gt;\n &lt;/select&gt;\n\n &lt;div class=\"select\"&gt;\n &lt;span class=\"value\"&gt;Cherry&lt;/span&gt;\n &lt;ul class=\"optList hidden\"&gt;\n &lt;li class=\"option\"&gt;Cherry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Lemon&lt;/li&gt;\n &lt;li class=\"option\"&gt;Banana&lt;/li&gt;\n &lt;li class=\"option\"&gt;Strawberry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Apple&lt;/li&gt;\n &lt;/ul&gt;\n &lt;/div&gt;\n &lt;/form&gt;\n&lt;/body&gt;\n</code></pre></div>\n<p>Second, we need two new classes to let us hide the unneeded element (that is, the \"real\" <a href=\"/es/docs/Web/HTML/Element/select\"><code>&lt;select&gt;</code></a> element if our script isn't running, or the custom widget if it is running). Note that by default, our HTML code hides our custom widget.</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">css</span></div><pre class=\"brush: css notranslate\"><code>.widget select,\n.no-widget .select {\n /* This CSS selector basically says:\n - either we have set the body class to \"widget\" and thus we hide the actual <a href=\"/es/docs/Web/HTML/Element/select\"><code>&lt;select&gt;</code></a> element\n - or we have not changed the body class, therefore the body class is still \"no-widget\",\n so the elements whose class is \"select\" must be hidden */\n position: absolute;\n left: -5000em;\n height: 0;\n overflow: hidden;\n}\n</code></pre></div>\n<p>Now we just need a JavaScript switch to determine if the script is running or not. This switch is very simple: if at page load time our script is running, it will remove the <code>no-widget</code> class and add the <code>widget</code> class, thereby swapping the visibility of the <a href=\"/es/docs/Web/HTML/Element/select\"><code>&lt;select&gt;</code></a> element and of the custom widget.</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>window.addEventListener(\"load\", function () {\n document.body.classList.remove(\"no-widget\");\n document.body.classList.add(\"widget\");\n});\n</code></pre></div>\n<h4 id=\"without_js\">Without JS</h4>\n<p>Check out the <a href=\"/en-US/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls/Example_2#no_js\" class=\"only-in-en-us\">full source code</a>.</p>\n<div class=\"code-example\"><pre class=\"brush: html hidden notranslate\"><code>&lt;form class=\"no-widget\"&gt;\n &lt;select name=\"myFruit\"&gt;\n &lt;option&gt;Cherry&lt;/option&gt;\n &lt;option&gt;Lemon&lt;/option&gt;\n &lt;option&gt;Banana&lt;/option&gt;\n &lt;option&gt;Strawberry&lt;/option&gt;\n &lt;option&gt;Apple&lt;/option&gt;\n &lt;/select&gt;\n\n &lt;div class=\"select\"&gt;\n &lt;span class=\"value\"&gt;Cherry&lt;/span&gt;\n &lt;ul class=\"optList hidden\"&gt;\n &lt;li class=\"option\"&gt;Cherry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Lemon&lt;/li&gt;\n &lt;li class=\"option\"&gt;Banana&lt;/li&gt;\n &lt;li class=\"option\"&gt;Strawberry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Apple&lt;/li&gt;\n &lt;/ul&gt;\n &lt;/div&gt;\n&lt;/form&gt;\n</code></pre></div>\n<div class=\"code-example\"><pre class=\"brush: css hidden notranslate\"><code>.widget select,\n.no-widget .select {\n position: absolute;\n left: -5000em;\n height: 0;\n overflow: hidden;\n}\n</code></pre></div>\n<div class=\"code-example\"><div class=\"example-header\"></div><iframe class=\"sample-code-frame\" title=\"Without JS sample\" id=\"frame_without_js\" width=\"120\" height=\"130\" src=\"about:blank\" data-live-path=\"/es/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls/\" data-live-id=\"without_js\" sandbox=\"allow-same-origin allow-scripts\" loading=\"lazy\"></iframe></div>\n<h4 id=\"with_js\">With JS</h4>\n<p>Check out the <a href=\"/en-US/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls/Example_2#js\" class=\"only-in-en-us\">full source code</a>.</p>\n<div class=\"code-example\"><pre class=\"brush: html hidden notranslate\"><code>&lt;form class=\"no-widget\"&gt;\n &lt;select name=\"myFruit\"&gt;\n &lt;option&gt;Cherry&lt;/option&gt;\n &lt;option&gt;Lemon&lt;/option&gt;\n &lt;option&gt;Banana&lt;/option&gt;\n &lt;option&gt;Strawberry&lt;/option&gt;\n &lt;option&gt;Apple&lt;/option&gt;\n &lt;/select&gt;\n\n &lt;div class=\"select\"&gt;\n &lt;span class=\"value\"&gt;Cherry&lt;/span&gt;\n &lt;ul class=\"optList hidden\"&gt;\n &lt;li class=\"option\"&gt;Cherry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Lemon&lt;/li&gt;\n &lt;li class=\"option\"&gt;Banana&lt;/li&gt;\n &lt;li class=\"option\"&gt;Strawberry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Apple&lt;/li&gt;\n &lt;/ul&gt;\n &lt;/div&gt;\n&lt;/form&gt;\n</code></pre></div>\n<div class=\"code-example\"><pre class=\"brush: css hidden notranslate\"><code>.widget select,\n.no-widget .select {\n position: absolute;\n left: -5000em;\n height: 0;\n overflow: hidden;\n}\n\n.select {\n position: relative;\n display: inline-block;\n}\n\n.select.active,\n.select:focus {\n box-shadow: 0 0 3px 1px #227755;\n outline: none;\n}\n\n.select .optList {\n position: absolute;\n top: 100%;\n left: 0;\n}\n\n.select .optList.hidden {\n max-height: 0;\n visibility: hidden;\n}\n\n.select {\n font-size: 0.625em; /* 10px */\n font-family: Verdana, Arial, sans-serif;\n\n box-sizing: border-box;\n\n padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */\n width: 10em; /* 100px */\n\n border: 0.2em solid #000; /* 2px */\n border-radius: 0.4em; /* 4px */\n\n box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */\n\n background: #f0f0f0;\n background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);\n}\n\n.select .value {\n display: inline-block;\n width: 100%;\n overflow: hidden;\n\n white-space: nowrap;\n text-overflow: ellipsis;\n vertical-align: top;\n}\n\n.select:after {\n content: \"▼\";\n position: absolute;\n z-index: 1;\n height: 100%;\n width: 2em; /* 20px */\n top: 0;\n right: 0;\n\n padding-top: 0.1em;\n\n box-sizing: border-box;\n\n text-align: center;\n\n border-left: 0.2em solid #000;\n border-radius: 0 0.1em 0.1em 0;\n\n background-color: #000;\n color: #fff;\n}\n\n.select .optList {\n z-index: 2;\n\n list-style: none;\n margin: 0;\n padding: 0;\n\n background: #f0f0f0;\n border: 0.2em solid #000;\n border-top-width: 0.1em;\n border-radius: 0 0 0.4em 0.4em;\n\n box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);\n\n box-sizing: border-box;\n\n min-width: 100%;\n max-height: 10em; /* 100px */\n overflow-y: auto;\n overflow-x: hidden;\n}\n\n.select .option {\n padding: 0.2em 0.3em;\n}\n\n.select .highlight {\n background: #000;\n color: #ffffff;\n}\n</code></pre></div>\n<div class=\"code-example\"><pre class=\"brush: js hidden notranslate\"><code>window.addEventListener(\"load\", () =&gt; {\n const form = document.querySelector(\"form\");\n\n form.classList.remove(\"no-widget\");\n form.classList.add(\"widget\");\n});\n</code></pre></div>\n<p>must be provided</p>\n<div class=\"notecard note\">\n<p><strong>Nota:</strong>\nIf you really want to make your code generic and reusable, instead of doing a class switch it's far better to just add the widget class to hide the <a href=\"/es/docs/Web/HTML/Element/select\"><code>&lt;select&gt;</code></a> elements, and to dynamically add the DOM tree representing the custom widget after every <a href=\"/es/docs/Web/HTML/Element/select\"><code>&lt;select&gt;</code></a> element in the page.</p>\n</div>"}},{"type":"prose","value":{"id":"making_the_job_easier","title":"Making the job easier","isH3":true,"content":"<p>In the code we are about to build, we will use the standard DOM API to do all the work we need. However, although DOM API support has gotten much better in browsers, there are always issues with legacy browsers (especially with good old Internet Explorer).</p>\n<p>If you want to avoid trouble with legacy browsers, there are two ways to do so: using a dedicated framework such as <a href=\"https://jquery.com/\" class=\"external\" target=\"_blank\">jQuery</a>, <a href=\"https://github.com/julienw/dollardom\" class=\"external\" target=\"_blank\">$dom</a>, <a href=\"http://prototypejs.org/\" class=\"external\" target=\"_blank\">prototype</a>, <a href=\"https://dojotoolkit.org/\" class=\"external\" target=\"_blank\">Dojo</a>, <a href=\"https://yuilibrary.com/\" class=\"external\" target=\"_blank\">YUI</a>, or the like, or by polyfilling the missing feature you want to use (which can easily be done through conditional loading, with the <a href=\"https://yepnopejs.com/\" class=\"external\" target=\"_blank\">yepnope</a> library for example).</p>\n<p>The features we plan to use are the following (ordered from the riskiest to the safest):</p>\n<ol>\n<li><a href=\"/es/docs/Web/API/Element/classList\" title=\"classList\"><code>classList</code></a></li>\n<li><a href=\"/es/docs/Web/API/EventTarget/addEventListener\" title=\"addEventListener\"><code>addEventListener</code></a></li>\n<li><a href=\"/es/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach\"><code>forEach</code></a> (This is not DOM but modern JavaScript)</li>\n<li><a href=\"/es/docs/Web/API/Element/querySelector\" title=\"querySelector\"><code>querySelector</code></a> and <a href=\"/en-US/docs/Web/API/Element/querySelectorAll\" title=\"querySelectorAll\" class=\"only-in-en-us\"><code>querySelectorAll</code></a></li>\n</ol>\n<p>Beyond the availability of those specific features, there is still one issue remaining before starting. The object returned by the <a href=\"/en-US/docs/Web/API/Element/querySelectorAll\" title=\"querySelectorAll()\" class=\"only-in-en-us\"><code>querySelectorAll()</code></a> function is a <a href=\"/es/docs/Web/API/NodeList\"><code>NodeList</code></a> rather than an <a href=\"/es/docs/Web/JavaScript/Reference/Global_Objects/Array\"><code>Array</code></a>. This is important because <code>Array</code> objects support the <a href=\"/es/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach\"><code>forEach</code></a> function, but <a href=\"/es/docs/Web/API/NodeList\"><code>NodeList</code></a> doesn't. Because <a href=\"/es/docs/Web/API/NodeList\"><code>NodeList</code></a> really looks like an <code>Array</code> and because <code>forEach</code> is so convenient to use, we can easily add the support of <code>forEach</code> to <a href=\"/es/docs/Web/API/NodeList\"><code>NodeList</code></a> in order to make our life easier, like so:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>NodeList.prototype.forEach = function (callback) {\n Array.prototype.forEach.call(this, callback);\n};\n</code></pre></div>\n<p>We weren't kidding when we said it's easy to do.</p>"}},{"type":"prose","value":{"id":"building_event_callbacks","title":"Building event callbacks","isH3":true,"content":"<p>The ground is ready, we can now start to define all the functions that will be used each time the user interacts with our widget.</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// This function will be used each time we want to deactivate a custom widget\n// It takes one parameter\n// select : the DOM node with the `select` class to deactivate\nfunction deactivateSelect(select) {\n // If the widget is not active there is nothing to do\n if (!select.classList.contains(\"active\")) return;\n\n // We need to get the list of options for the custom widget\n var optList = select.querySelector(\".optList\");\n\n // We close the list of option\n optList.classList.add(\"hidden\");\n\n // and we deactivate the custom widget itself\n select.classList.remove(\"active\");\n}\n\n// This function will be used each time the user wants to (de)activate the widget\n// It takes two parameters:\n// select : the DOM node with the `select` class to activate\n// selectList : the list of all the DOM nodes with the `select` class\nfunction activeSelect(select, selectList) {\n // If the widget is already active there is nothing to do\n if (select.classList.contains(\"active\")) return;\n\n // We have to turn off the active state on all custom widgets\n // Because the deactivateSelect function fulfill all the requirement of the\n // forEach callback function, we use it directly without using an intermediate\n // anonymous function.\n selectList.forEach(deactivateSelect);\n\n // And we turn on the active state for this specific widget\n select.classList.add(\"active\");\n}\n\n// This function will be used each time the user wants to open/closed the list of options\n// It takes one parameter:\n// select : the DOM node with the list to toggle\nfunction toggleOptList(select) {\n // The list is kept from the widget\n var optList = select.querySelector(\".optList\");\n\n // We change the class of the list to show/hide it\n optList.classList.toggle(\"hidden\");\n}\n\n// This function will be used each time we need to highlight an option\n// It takes two parameters:\n// select : the DOM node with the `select` class containing the option to highlight\n// option : the DOM node with the `option` class to highlight\nfunction highlightOption(select, option) {\n // We get the list of all option available for our custom select element\n var optionList = select.querySelectorAll(\".option\");\n\n // We remove the highlight from all options\n optionList.forEach(function (other) {\n other.classList.remove(\"highlight\");\n });\n\n // We highlight the right option\n option.classList.add(\"highlight\");\n}\n</code></pre></div>\n<p>That's all you need in order to handle the various states of the custom widget.</p>\n<p>Next, we bind these functions to the appropriate events:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// We handle the event binding when the document is loaded.\nwindow.addEventListener(\"load\", function () {\n var selectList = document.querySelectorAll(\".select\");\n\n // Each custom widget needs to be initialized\n selectList.forEach(function (select) {\n // as well as all its `option` elements\n var optionList = select.querySelectorAll(\".option\");\n\n // Each time a user hovers their mouse over an option, we highlight the given option\n optionList.forEach(function (option) {\n option.addEventListener(\"mouseover\", function () {\n // Note: the `select` and `option` variable are closures\n // available in the scope of our function call.\n highlightOption(select, option);\n });\n });\n\n // Each times the user click on a custom select element\n select.addEventListener(\"click\", function (event) {\n // Note: the `select` variable is a closure\n // available in the scope of our function call.\n\n // We toggle the visibility of the list of options\n toggleOptList(select);\n });\n\n // In case the widget gain focus\n // The widget gains the focus each time the user clicks on it or each time\n // they use the tabulation key to access the widget\n select.addEventListener(\"focus\", function (event) {\n // Note: the `select` and `selectList` variable are closures\n // available in the scope of our function call.\n\n // We activate the widget\n activeSelect(select, selectList);\n });\n\n // In case the widget loose focus\n select.addEventListener(\"blur\", function (event) {\n // Note: the `select` variable is a closure\n // available in the scope of our function call.\n\n // We deactivate the widget\n deactivateSelect(select);\n });\n });\n});\n</code></pre></div>\n<p>At that point, our widget will change state according to our design, but its value doesn't get updated yet. We'll handle that next.</p>\n<h4 id=\"live_example\">Live example</h4>\n<div class=\"code-example\"><pre class=\"brush: html hidden notranslate\"><code>&lt;form class=\"no-widget\"&gt;\n &lt;select name=\"myFruit\" tabindex=\"-1\"&gt;\n &lt;option&gt;Cherry&lt;/option&gt;\n &lt;option&gt;Lemon&lt;/option&gt;\n &lt;option&gt;Banana&lt;/option&gt;\n &lt;option&gt;Strawberry&lt;/option&gt;\n &lt;option&gt;Apple&lt;/option&gt;\n &lt;/select&gt;\n\n &lt;div class=\"select\" tabindex=\"0\"&gt;\n &lt;span class=\"value\"&gt;Cherry&lt;/span&gt;\n &lt;ul class=\"optList hidden\"&gt;\n &lt;li class=\"option\"&gt;Cherry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Lemon&lt;/li&gt;\n &lt;li class=\"option\"&gt;Banana&lt;/li&gt;\n &lt;li class=\"option\"&gt;Strawberry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Apple&lt;/li&gt;\n &lt;/ul&gt;\n &lt;/div&gt;\n&lt;/form&gt;\n</code></pre></div>\n<div class=\"code-example\"><pre class=\"brush: css hidden notranslate\"><code>.widget select,\n.no-widget .select {\n position: absolute;\n left: -5000em;\n height: 0;\n overflow: hidden;\n}\n\n.select {\n position: relative;\n display: inline-block;\n}\n\n.select.active,\n.select:focus {\n box-shadow: 0 0 3px 1px #227755;\n outline: none;\n}\n\n.select .optList {\n position: absolute;\n top: 100%;\n left: 0;\n}\n\n.select .optList.hidden {\n max-height: 0;\n visibility: hidden;\n}\n\n.select {\n font-size: 0.625em; /* 10px */\n font-family: Verdana, Arial, sans-serif;\n\n box-sizing: border-box;\n\n padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */\n width: 10em; /* 100px */\n\n border: 0.2em solid #000; /* 2px */\n border-radius: 0.4em; /* 4px */\n\n box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */\n\n background: #f0f0f0;\n background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);\n}\n\n.select .value {\n display: inline-block;\n width: 100%;\n overflow: hidden;\n\n white-space: nowrap;\n text-overflow: ellipsis;\n vertical-align: top;\n}\n\n.select:after {\n content: \"▼\";\n position: absolute;\n z-index: 1;\n height: 100%;\n width: 2em; /* 20px */\n top: 0;\n right: 0;\n\n padding-top: 0.1em;\n\n box-sizing: border-box;\n\n text-align: center;\n\n border-left: 0.2em solid #000;\n border-radius: 0 0.1em 0.1em 0;\n\n background-color: #000;\n color: #fff;\n}\n\n.select .optList {\n z-index: 2;\n\n list-style: none;\n margin: 0;\n padding: 0;\n\n background: #f0f0f0;\n border: 0.2em solid #000;\n border-top-width: 0.1em;\n border-radius: 0 0 0.4em 0.4em;\n\n box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);\n\n box-sizing: border-box;\n\n min-width: 100%;\n max-height: 10em; /* 100px */\n overflow-y: auto;\n overflow-x: hidden;\n}\n\n.select .option {\n padding: 0.2em 0.3em;\n}\n\n.select .highlight {\n background: #000;\n color: #ffffff;\n}\n</code></pre></div>\n<div class=\"code-example\"><pre class=\"brush: js hidden notranslate\"><code>function deactivateSelect(select) {\n if (!select.classList.contains(\"active\")) return;\n\n const optList = select.querySelector(\".optList\");\n\n optList.classList.add(\"hidden\");\n select.classList.remove(\"active\");\n}\n\nfunction activeSelect(select, selectList) {\n if (select.classList.contains(\"active\")) return;\n\n selectList.forEach(deactivateSelect);\n select.classList.add(\"active\");\n}\n\nfunction toggleOptList(select, show) {\n const optList = select.querySelector(\".optList\");\n\n optList.classList.toggle(\"hidden\");\n}\n\nfunction highlightOption(select, option) {\n const optionList = select.querySelectorAll(\".option\");\n\n optionList.forEach((other) =&gt; {\n other.classList.remove(\"highlight\");\n });\n\n option.classList.add(\"highlight\");\n}\n\nwindow.addEventListener(\"load\", () =&gt; {\n const form = document.querySelector(\"form\");\n\n form.classList.remove(\"no-widget\");\n form.classList.add(\"widget\");\n});\n\nwindow.addEventListener(\"load\", () =&gt; {\n const selectList = document.querySelectorAll(\".select\");\n\n selectList.forEach((select) =&gt; {\n const optionList = select.querySelectorAll(\".option\");\n\n optionList.forEach((option) =&gt; {\n option.addEventListener(\"mouseover\", () =&gt; {\n highlightOption(select, option);\n });\n });\n\n select.addEventListener(\n \"click\",\n (event) =&gt; {\n toggleOptList(select);\n },\n false,\n );\n\n select.addEventListener(\"focus\", (event) =&gt; {\n activeSelect(select, selectList);\n });\n\n select.addEventListener(\"blur\", (event) =&gt; {\n deactivateSelect(select);\n });\n\n select.addEventListener(\"keyup\", (event) =&gt; {\n if (event.keyCode === 27) {\n deactivateSelect(select);\n }\n });\n });\n});\n</code></pre></div>\n<figure class=\"table-container\"><table>\n<thead>\n<tr>\n<th>Live example</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>must be provided</td>\n</tr>\n</tbody>\n</table></figure>\n<pre class=\"notranslate\"> |\n</pre>\n<p>| <a href=\"/en-US/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls/Example_3\" class=\"only-in-en-us\">Check out the source code</a> |</p>"}},{"type":"prose","value":{"id":"handling_the_widgets_value","title":"Handling the widget's value","isH3":true,"content":"<p>Now that our widget is working, we have to add code to update its value according to user input and make it possible to send the value along with form data.</p>\n<p>The easiest way to do this is to use a native widget under the hood. Such a widget will keep track of the value with all the built-in controls provided by the browser, and the value will be sent as usual when a form is submitted. There's no point in reinventing the wheel when we can have all this done for us.</p>\n<p>As seen previously, we already use a native select widget as a fallback for accessibility reasons; we can simply synchronize its value with that of our custom widget:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// This function updates the displayed value and synchronizes it with the native widget.\n// It takes two parameters:\n// select : the DOM node with the class `select` containing the value to update\n// index : the index of the value to be selected\nfunction updateValue(select, index) {\n // We need to get the native widget for the given custom widget\n // In our example, that native widget is a sibling of the custom widget\n var nativeWidget = select.previousElementSibling;\n\n // We also need to get the value placeholder of our custom widget\n var value = select.querySelector(\".value\");\n\n // And we need the whole list of options\n var optionList = select.querySelectorAll(\".option\");\n\n // We set the selected index to the index of our choice\n nativeWidget.selectedIndex = index;\n\n // We update the value placeholder accordingly\n value.innerHTML = optionList[index].innerHTML;\n\n // And we highlight the corresponding option of our custom widget\n highlightOption(select, optionList[index]);\n}\n\n// This function returns the current selected index in the native widget\n// It takes one parameter:\n// select : the DOM node with the class `select` related to the native widget\nfunction getIndex(select) {\n // We need to access the native widget for the given custom widget\n // In our example, that native widget is a sibling of the custom widget\n var nativeWidget = select.previousElementSibling;\n\n return nativeWidget.selectedIndex;\n}\n</code></pre></div>\n<p>With these two functions, we can bind the native widgets to the custom ones:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// We handle event binding when the document is loaded.\nwindow.addEventListener(\"load\", function () {\n var selectList = document.querySelectorAll(\".select\");\n\n // Each custom widget needs to be initialized\n selectList.forEach(function (select) {\n var optionList = select.querySelectorAll(\".option\"),\n selectedIndex = getIndex(select);\n\n // We make our custom widget focusable\n select.tabIndex = 0;\n\n // We make the native widget no longer focusable\n select.previousElementSibling.tabIndex = -1;\n\n // We make sure that the default selected value is correctly displayed\n updateValue(select, selectedIndex);\n\n // Each time a user clicks on an option, we update the value accordingly\n optionList.forEach(function (option, index) {\n option.addEventListener(\"click\", function (event) {\n updateValue(select, index);\n });\n });\n\n // Each time a user uses their keyboard on a focused widget, we update the value accordingly\n select.addEventListener(\"keyup\", function (event) {\n var length = optionList.length,\n index = getIndex(select);\n\n // When the user hits the down arrow, we jump to the next option\n if (event.keyCode === 40 &amp;&amp; index &lt; length - 1) {\n index++;\n }\n\n // When the user hits the up arrow, we jump to the previous option\n if (event.keyCode === 38 &amp;&amp; index &gt; 0) {\n index--;\n }\n\n updateValue(select, index);\n });\n });\n});\n</code></pre></div>\n<p>In the code above, it's worth noting the use of the <a href=\"/en-US/docs/Web/API/HTMLElement/tabIndex\" class=\"only-in-en-us\"><code>tabIndex</code></a> property. Using this property is necessary to ensure that the native widget will never gain focus, and to make sure that our custom widget gains focus when the user uses his keyboard or his mouse.</p>\n<p>With that, we're done! Here's the result:</p>\n<div class=\"code-example\"><pre class=\"brush: html hidden notranslate\"><code>&lt;form class=\"no-widget\"&gt;\n &lt;select name=\"myFruit\"&gt;\n &lt;option&gt;Cherry&lt;/option&gt;\n &lt;option&gt;Lemon&lt;/option&gt;\n &lt;option&gt;Banana&lt;/option&gt;\n &lt;option&gt;Strawberry&lt;/option&gt;\n &lt;option&gt;Apple&lt;/option&gt;\n &lt;/select&gt;\n\n &lt;div class=\"select\"&gt;\n &lt;span class=\"value\"&gt;Cherry&lt;/span&gt;\n &lt;ul class=\"optList hidden\"&gt;\n &lt;li class=\"option\"&gt;Cherry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Lemon&lt;/li&gt;\n &lt;li class=\"option\"&gt;Banana&lt;/li&gt;\n &lt;li class=\"option\"&gt;Strawberry&lt;/li&gt;\n &lt;li class=\"option\"&gt;Apple&lt;/li&gt;\n &lt;/ul&gt;\n &lt;/div&gt;\n&lt;/form&gt;\n</code></pre></div>\n<div class=\"code-example\"><pre class=\"brush: css hidden notranslate\"><code>.widget select,\n.no-widget .select {\n position: absolute;\n left: -5000em;\n height: 0;\n overflow: hidden;\n}\n\n.select {\n position: relative;\n display: inline-block;\n}\n\n.select.active,\n.select:focus {\n box-shadow: 0 0 3px 1px #227755;\n outline: none;\n}\n\n.select .optList {\n position: absolute;\n top: 100%;\n left: 0;\n}\n\n.select .optList.hidden {\n max-height: 0;\n visibility: hidden;\n}\n\n.select {\n font-size: 0.625em; /* 10px */\n font-family: Verdana, Arial, sans-serif;\n\n box-sizing: border-box;\n\n padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */\n width: 10em; /* 100px */\n\n border: 0.2em solid #000; /* 2px */\n border-radius: 0.4em; /* 4px */\n\n box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */\n\n background: #f0f0f0;\n background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);\n}\n\n.select .value {\n display: inline-block;\n width: 100%;\n overflow: hidden;\n\n white-space: nowrap;\n text-overflow: ellipsis;\n vertical-align: top;\n}\n\n.select:after {\n content: \"▼\";\n position: absolute;\n z-index: 1;\n height: 100%;\n width: 2em; /* 20px */\n top: 0;\n right: 0;\n\n padding-top: 0.1em;\n\n box-sizing: border-box;\n\n text-align: center;\n\n border-left: 0.2em solid #000;\n border-radius: 0 0.1em 0.1em 0;\n\n background-color: #000;\n color: #fff;\n}\n\n.select .optList {\n z-index: 2;\n\n list-style: none;\n margin: 0;\n padding: 0;\n\n background: #f0f0f0;\n border: 0.2em solid #000;\n border-top-width: 0.1em;\n border-radius: 0 0 0.4em 0.4em;\n\n box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);\n\n box-sizing: border-box;\n\n min-width: 100%;\n max-height: 10em; /* 100px */\n overflow-y: auto;\n overflow-x: hidden;\n}\n\n.select .option {\n padding: 0.2em 0.3em;\n}\n\n.select .highlight {\n background: #000;\n color: #ffffff;\n}\n</code></pre></div>\n<div class=\"code-example\"><pre class=\"brush: js hidden notranslate\"><code>function deactivateSelect(select) {\n if (!select.classList.contains(\"active\")) return;\n\n const optList = select.querySelector(\".optList\");\n\n optList.classList.add(\"hidden\");\n select.classList.remove(\"active\");\n}\n\nfunction activeSelect(select, selectList) {\n if (select.classList.contains(\"active\")) return;\n\n selectList.forEach(deactivateSelect);\n select.classList.add(\"active\");\n}\n\nfunction toggleOptList(select, show) {\n const optList = select.querySelector(\".optList\");\n\n optList.classList.toggle(\"hidden\");\n}\n\nfunction highlightOption(select, option) {\n const optionList = select.querySelectorAll(\".option\");\n\n optionList.forEach((other) =&gt; {\n other.classList.remove(\"highlight\");\n });\n\n option.classList.add(\"highlight\");\n}\n\nfunction updateValue(select, index) {\n const nativeWidget = select.previousElementSibling;\n const value = select.querySelector(\".value\");\n const optionList = select.querySelectorAll(\".option\");\n\n nativeWidget.selectedIndex = index;\n value.innerHTML = optionList[index].innerHTML;\n highlightOption(select, optionList[index]);\n}\n\nfunction getIndex(select) {\n const nativeWidget = select.previousElementSibling;\n\n return nativeWidget.selectedIndex;\n}\n\nwindow.addEventListener(\"load\", () =&gt; {\n const form = document.querySelector(\"form\");\n\n form.classList.remove(\"no-widget\");\n form.classList.add(\"widget\");\n});\n\nwindow.addEventListener(\"load\", () =&gt; {\n const selectList = document.querySelectorAll(\".select\");\n\n selectList.forEach((select) =&gt; {\n const optionList = select.querySelectorAll(\".option\");\n\n optionList.forEach((option) =&gt; {\n option.addEventListener(\"mouseover\", () =&gt; {\n highlightOption(select, option);\n });\n });\n\n select.addEventListener(\"click\", (event) =&gt; {\n toggleOptList(select);\n });\n\n select.addEventListener(\"focus\", (event) =&gt; {\n activeSelect(select, selectList);\n });\n\n select.addEventListener(\"blur\", (event) =&gt; {\n deactivateSelect(select);\n });\n });\n});\n\nwindow.addEventListener(\"load\", () =&gt; {\n const selectList = document.querySelectorAll(\".select\");\n\n selectList.forEach((select) =&gt; {\n const optionList = select.querySelectorAll(\".option\");\n const selectedIndex = getIndex(select);\n\n select.tabIndex = 0;\n select.previousElementSibling.tabIndex = -1;\n\n updateValue(select, selectedIndex);\n\n optionList.forEach((option, index) =&gt; {\n option.addEventListener(\"click\", (event) =&gt; {\n updateValue(select, index);\n });\n });\n\n select.addEventListener(\"keyup\", (event) =&gt; {\n let index = getIndex(select);\n\n if (event.key === \"Escape\") {\n deactivateSelect(select);\n }\n if (event.key === \"ArrowDown\" &amp;&amp; index &lt; optionList.length - 1) {\n index++;\n }\n if (event.key === \"ArrowUp\" &amp;&amp; index &gt; 0) {\n index--;\n }\n\n updateValue(select, index);\n });\n });\n});\n</code></pre></div>\n<figure class=\"table-container\"><table>\n<thead>\n<tr>\n<th>Live example</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>must be provided</td>\n</tr>\n</tbody>\n</table></figure>\n<pre class=\"notranslate\"> |\n</pre>\n<p>| <a href=\"/en-US/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls/Example_4\" class=\"only-in-en-us\">Check out the source code</a> |</p>\n<p>But wait a second, are we really done?</p>"}},{"type":"prose","value":{"id":"make_it_accessible","title":"Make it accessible","isH3":false,"content":"<p>We have built something that works and though we're far from a fully-featured select box, it works nicely. But what we've done is nothing more than fiddle with the DOM. It has no real semantics, and even though it looks like a select box, from the browser's point of view it isn't one, so assistive technologies won't be able to understand it's a select box. In short, this pretty new select box isn't accessible!</p>\n<p>Fortunately, there is a solution and it's called <a href=\"/es/docs/Web/Accessibility/ARIA\">ARIA</a>. ARIA stands for \"Accessible Rich Internet Application\", and it's <a href=\"https://www.w3.org/TR/wai-aria/\" class=\"external\" target=\"_blank\">a W3C specification</a> specifically designed for what we are doing here: making web applications and custom widgets accessible. It's basically a set of attributes that extend HTML so that we can better describe roles, states and properties as though the element we've just devised was the native element it tries to pass for. Using these attributes is dead simple, so let's do it.</p>"}},{"type":"prose","value":{"id":"the_role_attribute","title":"The <code>role</code> attribute","isH3":true,"content":"<p>The key attribute used by <a href=\"/es/docs/Web/Accessibility/ARIA\">ARIA</a> is the <a href=\"/es/docs/Web/Accessibility/ARIA/ARIA_Techniques\"><code>role</code></a> attribute. The <a href=\"/es/docs/Web/Accessibility/ARIA/ARIA_Techniques\"><code>role</code></a> attribute accepts a value that defines what an element is used for. Each role defines its own requirements and behaviors. In our example, we will use the <a href=\"/en-US/docs/Web/Accessibility/ARIA/Roles/listbox_role\" class=\"only-in-en-us\"><code>listbox</code></a> role. It's a \"composite role\", which means elements with that role expect to have children, each with a specific role (in this case, at least one child with the <code>option</code> role).</p>\n<p>It's also worth noting that ARIA defines roles that are applied by default to standard HTML markup. For example, the <a href=\"/es/docs/Web/HTML/Element/table\"><code>&lt;table&gt;</code></a> element matches the role <code>grid</code>, and the <a href=\"/es/docs/Web/HTML/Element/ul\"><code>&lt;ul&gt;</code></a> element matches the role <code>list</code>. Because we use a <a href=\"/es/docs/Web/HTML/Element/ul\"><code>&lt;ul&gt;</code></a> element, we want to make sure the <code>listbox</code> role of our widget will supersede the <code>list</code> role of the <a href=\"/es/docs/Web/HTML/Element/ul\"><code>&lt;ul&gt;</code></a> element. To that end, we will use the role <code>presentation</code>. This role is designed to let us indicate that an element has no special meaning, and is used solely to present information. We will apply it to our <a href=\"/es/docs/Web/HTML/Element/ul\"><code>&lt;ul&gt;</code></a> element.</p>\n<p>To support the <a href=\"/en-US/docs/Web/Accessibility/ARIA/Roles/listbox_role\" class=\"only-in-en-us\"><code>listbox</code></a> role, we just have to update our HTML like this:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">html</span></div><pre class=\"brush: html notranslate\"><code>&lt;!-- We add the role=\"listbox\" attribute to our top element --&gt;\n&lt;div class=\"select\" role=\"listbox\"&gt;\n &lt;span class=\"value\"&gt;Cherry&lt;/span&gt;\n &lt;!-- We also add the role=\"presentation\" to the ul element --&gt;\n &lt;ul class=\"optList\" role=\"presentation\"&gt;\n &lt;!-- And we add the role=\"option\" attribute to all the li elements --&gt;\n &lt;li role=\"option\" class=\"option\"&gt;Cherry&lt;/li&gt;\n &lt;li role=\"option\" class=\"option\"&gt;Lemon&lt;/li&gt;\n &lt;li role=\"option\" class=\"option\"&gt;Banana&lt;/li&gt;\n &lt;li role=\"option\" class=\"option\"&gt;Strawberry&lt;/li&gt;\n &lt;li role=\"option\" class=\"option\"&gt;Apple&lt;/li&gt;\n &lt;/ul&gt;\n&lt;/div&gt;\n</code></pre></div>\n<div class=\"notecard note\">\n<p><strong>Nota:</strong>\nIncluding both the <code>role</code> attribute and a <code>class</code> attribute is only necessary if you want to support legacy browsers that do not support the <a href=\"/es/docs/Web/CSS/Attribute_selectors\">CSS attribute selectors</a>.</p>\n</div>"}},{"type":"prose","value":{"id":"the_aria-selected_attribute","title":"The <code>aria-selected</code> attribute","isH3":true,"content":"<p>Using the <a href=\"/es/docs/Web/Accessibility/ARIA/ARIA_Techniques\"><code>role</code></a> attribute is not enough. <a href=\"/es/docs/Web/Accessibility/ARIA\">ARIA</a> also provides many states and property attributes. The more and better you use them, the better your widget will be understood by assistive technologies. In our case, we will limit our usage to one attribute: <code>aria-selected</code>.</p>\n<p>The <code>aria-selected</code> attribute is used to mark which option is currently selected; this lets assistive technologies inform the user what the current selection is. We will use it dynamically with JavaScript to mark the selected option each time the user chooses one. To that end, we need to revise our <code>updateValue()</code> function:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>function updateValue(select, index) {\n var nativeWidget = select.previousElementSibling;\n var value = select.querySelector(\".value\");\n var optionList = select.querySelectorAll(\".option\");\n\n // We make sure that all the options are not selected\n optionList.forEach(function (other) {\n other.setAttribute(\"aria-selected\", \"false\");\n });\n\n // We make sure the chosen option is selected\n optionList[index].setAttribute(\"aria-selected\", \"true\");\n\n nativeWidget.selectedIndex = index;\n value.innerHTML = optionList[index].innerHTML;\n highlightOption(select, optionList[index]);\n}\n</code></pre></div>\n<p>Here is the final result of all these changes (you'll get a better feel for this by trying it with an assistive technology such as <a href=\"http://www.nvda-project.org/\" class=\"external\" target=\"_blank\">NVDA</a> or <a href=\"https://www.apple.com/accessibility/voiceover/\" class=\"external\" target=\"_blank\">VoiceOver</a>):</p>\n<div class=\"code-example\"><pre class=\"brush: html hidden notranslate\"><code>&lt;form class=\"no-widget\"&gt;\n &lt;select name=\"myFruit\"&gt;\n &lt;option&gt;Cherry&lt;/option&gt;\n &lt;option&gt;Lemon&lt;/option&gt;\n &lt;option&gt;Banana&lt;/option&gt;\n &lt;option&gt;Strawberry&lt;/option&gt;\n &lt;option&gt;Apple&lt;/option&gt;\n &lt;/select&gt;\n\n &lt;div class=\"select\" role=\"listbox\"&gt;\n &lt;span class=\"value\"&gt;Cherry&lt;/span&gt;\n &lt;ul class=\"optList hidden\" role=\"presentation\"&gt;\n &lt;li class=\"option\" role=\"option\" aria-selected=\"true\"&gt;Cherry&lt;/li&gt;\n &lt;li class=\"option\" role=\"option\"&gt;Lemon&lt;/li&gt;\n &lt;li class=\"option\" role=\"option\"&gt;Banana&lt;/li&gt;\n &lt;li class=\"option\" role=\"option\"&gt;Strawberry&lt;/li&gt;\n &lt;li class=\"option\" role=\"option\"&gt;Apple&lt;/li&gt;\n &lt;/ul&gt;\n &lt;/div&gt;\n&lt;/form&gt;\n</code></pre></div>\n<div class=\"code-example\"><pre class=\"brush: css hidden notranslate\"><code>.widget select,\n.no-widget .select {\n position: absolute;\n left: -5000em;\n height: 0;\n overflow: hidden;\n}\n\n.select {\n position: relative;\n display: inline-block;\n}\n\n.select.active,\n.select:focus {\n box-shadow: 0 0 3px 1px #227755;\n outline: none;\n}\n\n.select .optList {\n position: absolute;\n top: 100%;\n left: 0;\n}\n\n.select .optList.hidden {\n max-height: 0;\n visibility: hidden;\n}\n\n.select {\n font-size: 0.625em; /* 10px */\n font-family: Verdana, Arial, sans-serif;\n\n box-sizing: border-box;\n\n padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */\n width: 10em; /* 100px */\n\n border: 0.2em solid #000; /* 2px */\n border-radius: 0.4em; /* 4px */\n\n box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */\n\n background: #f0f0f0;\n background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);\n}\n\n.select .value {\n display: inline-block;\n width: 100%;\n overflow: hidden;\n\n white-space: nowrap;\n text-overflow: ellipsis;\n vertical-align: top;\n}\n\n.select:after {\n content: \"▼\";\n position: absolute;\n z-index: 1;\n height: 100%;\n width: 2em; /* 20px */\n top: 0;\n right: 0;\n\n padding-top: 0.1em;\n\n box-sizing: border-box;\n\n text-align: center;\n\n border-left: 0.2em solid #000;\n border-radius: 0 0.1em 0.1em 0;\n\n background-color: #000;\n color: #fff;\n}\n\n.select .optList {\n z-index: 2;\n\n list-style: none;\n margin: 0;\n padding: 0;\n\n background: #f0f0f0;\n border: 0.2em solid #000;\n border-top-width: 0.1em;\n border-radius: 0 0 0.4em 0.4em;\n\n box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);\n\n box-sizing: border-box;\n\n min-width: 100%;\n max-height: 10em; /* 100px */\n overflow-y: auto;\n overflow-x: hidden;\n}\n\n.select .option {\n padding: 0.2em 0.3em;\n}\n\n.select .highlight {\n background: #000;\n color: #ffffff;\n}\n</code></pre></div>\n<div class=\"code-example\"><pre class=\"brush: js hidden notranslate\"><code>function deactivateSelect(select) {\n if (!select.classList.contains(\"active\")) return;\n\n const optList = select.querySelector(\".optList\");\n\n optList.classList.add(\"hidden\");\n select.classList.remove(\"active\");\n}\n\nfunction activeSelect(select, selectList) {\n if (select.classList.contains(\"active\")) return;\n\n selectList.forEach(deactivateSelect);\n select.classList.add(\"active\");\n}\n\nfunction toggleOptList(select, show) {\n const optList = select.querySelector(\".optList\");\n\n optList.classList.toggle(\"hidden\");\n}\n\nfunction highlightOption(select, option) {\n const optionList = select.querySelectorAll(\".option\");\n\n optionList.forEach((other) =&gt; {\n other.classList.remove(\"highlight\");\n });\n\n option.classList.add(\"highlight\");\n}\n\nfunction updateValue(select, index) {\n const nativeWidget = select.previousElementSibling;\n const value = select.querySelector(\".value\");\n const optionList = select.querySelectorAll(\".option\");\n\n optionList.forEach((other) =&gt; {\n other.setAttribute(\"aria-selected\", \"false\");\n });\n\n optionList[index].setAttribute(\"aria-selected\", \"true\");\n\n nativeWidget.selectedIndex = index;\n value.innerHTML = optionList[index].innerHTML;\n highlightOption(select, optionList[index]);\n}\n\nfunction getIndex(select) {\n const nativeWidget = select.previousElementSibling;\n\n return nativeWidget.selectedIndex;\n}\n\nwindow.addEventListener(\"load\", () =&gt; {\n const form = document.querySelector(\"form\");\n\n form.classList.remove(\"no-widget\");\n form.classList.add(\"widget\");\n});\n\nwindow.addEventListener(\"load\", () =&gt; {\n const selectList = document.querySelectorAll(\".select\");\n\n selectList.forEach((select) =&gt; {\n const optionList = select.querySelectorAll(\".option\");\n const selectedIndex = getIndex(select);\n\n select.tabIndex = 0;\n select.previousElementSibling.tabIndex = -1;\n\n updateValue(select, selectedIndex);\n\n optionList.forEach((option, index) =&gt; {\n option.addEventListener(\"mouseover\", () =&gt; {\n highlightOption(select, option);\n });\n\n option.addEventListener(\"click\", (event) =&gt; {\n updateValue(select, index);\n });\n });\n\n select.addEventListener(\"click\", (event) =&gt; {\n toggleOptList(select);\n });\n\n select.addEventListener(\"focus\", (event) =&gt; {\n activeSelect(select, selectList);\n });\n\n select.addEventListener(\"blur\", (event) =&gt; {\n deactivateSelect(select);\n });\n\n select.addEventListener(\"keyup\", (event) =&gt; {\n let index = getIndex(select);\n\n if (event.keyCode === 27) {\n deactivateSelect(select);\n }\n if (event.keyCode === 40 &amp;&amp; index &lt; optionList.length - 1) {\n index++;\n }\n if (event.keyCode === 38 &amp;&amp; index &gt; 0) {\n index--;\n }\n\n updateValue(select, index);\n });\n });\n});\n</code></pre></div>\n<figure class=\"table-container\"><table>\n<thead>\n<tr>\n<th>Live example</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>must be provided</td>\n</tr>\n</tbody>\n</table></figure>\n<pre class=\"notranslate\"> |\n</pre>\n<p>| <a href=\"/en-US/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls/Example_5\" class=\"only-in-en-us\">Check out the final source code</a> |</p>"}},{"type":"prose","value":{"id":"conclusion","title":"Conclusion","isH3":false,"content":"<p>We have seen all the basics of building a custom form widget, but as you can see it's not trivial to do, and often it's better and easier to rely on third-party libraries instead of coding them from scratch yourself (unless, of course, your goal is to build such a library).</p>\n<p>Here are a few libraries you should consider before coding your own:</p>\n<ul>\n<li><a href=\"https://jqueryui.com/\" class=\"external\" target=\"_blank\">jQuery UI</a></li>\n<li><a href=\"https://github.com/marghoobsuleman/ms-Dropdown\" class=\"external\" target=\"_blank\">msDropDown</a></li>\n<li><a href=\"https://www.emblematiq.com/lab/niceforms/\" class=\"external\" target=\"_blank\">Nice Forms</a></li>\n<li><a href=\"https://www.google.fr/search?q=HTML+custom+form+controls&amp;ie=utf-8&amp;oe=utf-8&amp;aq=t&amp;rls=org.mozilla:fr:official&amp;client=firefox-a\" class=\"external\" target=\"_blank\">And many more…</a></li>\n</ul>\n<p>If you want to move forward, the code in this example needs some improvement before it becomes generic and reusable. This is an exercise you can try to perform. Two hints to help you in this: the first argument for all our functions is the same, which means those functions need the same context. Building an object to share that context would be wise. Also, you need to make it feature-proof; that is, it needs to be able to work better with a variety of browsers whose compatibility with the Web standards they use vary. Have fun!</p>\n<p>page(Doc) not found /es/docs/Learn/HTML/Forms/Form_validation</p>"}}],"isActive":true,"isMarkdown":true,"isTranslated":true,"locale":"es","mdn_url":"/es/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls","modified":"2024-12-21T04:32:59.000Z","native":"Español","noIndexing":false,"other_translations":[{"locale":"en-US","title":"How to build custom form controls","native":"English (US)"},{"locale":"de","title":"Anleitung zur Erstellung benutzerdefinierter Formularsteuerungen","native":"Deutsch"},{"locale":"fr","title":"Comment construire des widgets de formulaires personnalisés","native":"Français"},{"locale":"ja","title":"カスタムフォームコントロールの作成方法","native":"日本語"},{"locale":"ru","title":"Как создавать пользовательские виджеты форм","native":"Русский"},{"locale":"zh-CN","title":"如何构建自定义表单控件","native":"中文 (简体)"}],"pageTitle":"Cómo crear widgets de formularios personalizados - Aprende desarrollo web | MDN","parents":[{"uri":"/es/docs/Learn_web_development","title":"Aprende desarrollo web"},{"uri":"/en-US/docs/Learn_web_development/Extensions","title":"Extension modules"},{"uri":"/es/docs/Learn_web_development/Extensions/Forms","title":"Pilares de los formularios web"},{"uri":"/es/docs/Learn_web_development/Extensions/Forms/How_to_build_custom_form_controls","title":"Cómo crear widgets de formularios personalizados"}],"popularity":null,"short_title":"Cómo crear widgets de formularios personalizados","sidebarHTML":"<ol><li class=\"section\"><a href=\"/en-US/docs/Learn_web_development/Getting_started\" class=\"only-in-en-us\">Getting started modules</a></li><li class=\"toggle\"><details><summary><a href=\"/en-US/docs/Learn_web_development/Getting_started/Environment_setup\" class=\"only-in-en-us\">Environment setup</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Getting_started/Environment_setup/Installing_software\">Instalación de software básico</a></li><li><a href=\"/es/docs/Learn_web_development/Getting_started/Environment_setup/Browsing_the_web\">¿Cuál es la diferencia entre la página web, el sitio web, el servidor web y el motor de búsqueda?</a></li><li><a href=\"/en-US/docs/Learn_web_development/Getting_started/Environment_setup/Code_editors\" class=\"only-in-en-us\">Code editors</a></li><li><a href=\"/es/docs/Learn_web_development/Getting_started/Environment_setup/Dealing_with_files\">Manejo de archivos</a></li><li><a href=\"/en-US/docs/Learn_web_development/Getting_started/Environment_setup/Command_line\" class=\"only-in-en-us\">Command line crash course</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Getting_started/Your_first_website\">Your first website</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Getting_started/Your_first_website/What_will_your_website_look_like\">¿Cuál será la apariencia de tu sitio Web?</a></li><li><a href=\"/es/docs/Learn_web_development/Getting_started/Your_first_website/Creating_the_content\">Conceptos básicos de HTML</a></li><li><a href=\"/es/docs/Learn_web_development/Getting_started/Your_first_website/Styling_the_content\">CSS básico</a></li><li><a href=\"/es/docs/Learn_web_development/Getting_started/Your_first_website/Adding_interactivity\">Fundamentos de JavaScript</a></li><li><a href=\"/es/docs/Learn_web_development/Getting_started/Your_first_website/Publishing_your_website\">Publicar tu sitio web</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/en-US/docs/Learn_web_development/Getting_started/Web_standards\" class=\"only-in-en-us\">Web standards</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Getting_started/Web_standards/How_the_web_works\">Cómo funciona la web</a></li><li><a href=\"/es/docs/Learn_web_development/Getting_started/Web_standards/The_web_standards_model\">La web y los estándares web</a></li><li><a href=\"/en-US/docs/Learn_web_development/Getting_started/Web_standards/How_browsers_load_websites\" class=\"only-in-en-us\">How browsers load websites</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/en-US/docs/Learn_web_development/Getting_started/Soft_skills\" class=\"only-in-en-us\">Soft skills</a></summary><ol><li><a href=\"/en-US/docs/Learn_web_development/Getting_started/Soft_skills/Research_and_learning\" class=\"only-in-en-us\">Research and learning</a></li><li><a href=\"/en-US/docs/Learn_web_development/Getting_started/Soft_skills/Collaboration_and_teamwork\" class=\"only-in-en-us\">Collaboration and teamwork</a></li><li><a href=\"/en-US/docs/Learn_web_development/Getting_started/Soft_skills/Workflows_and_processes\" class=\"only-in-en-us\">Workflows and processes</a></li><li><a href=\"/en-US/docs/Learn_web_development/Getting_started/Soft_skills/Job_interviews\" class=\"only-in-en-us\">Succeeding in job interviews</a></li></ol></details></li><li class=\"section\"><a href=\"/en-US/docs/Learn_web_development/Core\" class=\"only-in-en-us\">Core modules</a></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Core/Structuring_content\">Structuring content with HTML</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Core/Structuring_content/Basic_HTML_syntax\">Primeros pasos con HTML</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Structuring_content/Webpage_metadata\">¿Qué hay en la cabecera? Metadatos en HTML</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Structuring_content/Headings_and_paragraphs\">Fundamentos de texto en HTML</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Structuring_content/Emphasis_and_importance\" class=\"only-in-en-us\">Emphasis and importance</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Structuring_content/Lists\" class=\"only-in-en-us\">Lists</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Structuring_content/Structuring_documents\">Estructura web y documentación</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Structuring_content/Advanced_text_features\">Formateo de texto avanzado</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Structuring_content/Creating_links\">Crear hipervínculos</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Structuring_content/Marking_up_a_letter\">Marcando una Carta</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Structuring_content/Structuring_a_page_of_content\">Estructuración de una Página de contenido</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Structuring_content/HTML_images\">Imágenes en HTML</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Structuring_content/HTML_video_and_audio\">Contenido de audio y video</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Structuring_content/Mozilla_splash_page\">Página de bienvenida de Mozilla</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Structuring_content/HTML_table_basics\">Conceptos básicos de las tablas HTML</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Structuring_content/Table_accessibility\">Funciones avanzadas de las tablas HTML y accesibilidad</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Structuring_content/Planet_data_table\">Evaluación: Estructurando datos planetarios</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Structuring_content/HTML_forms\" class=\"only-in-en-us\">Forms and buttons in HTML</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Structuring_content/Debugging_HTML\">Depurar el HTML</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Core/Styling_basics\">CSS styling basics</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/What_is_CSS\">Cómo funciona CSS</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/Getting_started\">Empezar con CSS</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/Styling_a_bio_page\">Usa tu nuevo conocimiento</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/Basic_selectors\">Selectores CSS</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/Attribute_selectors\">Selectores de atributo</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/Pseudo_classes_and_elements\">Pseudoclases y pseudoelementos</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/Combinators\">Combinadores</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/Box_model\">El modelo de caja</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/Handling_conflicts\">Cascada y herencia</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/Values_and_units\">Valores y unidades CSS</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/Sizing\">Dimensionar elementos en CSS</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/Backgrounds_and_borders\">Fondos y bordes</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/Overflow\">Contenido desbordado</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/Images_media_forms\">Imágenes, medios y elementos de formulario</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/Tables\">Estilizando tablas</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/Debugging_CSS\">Depurar el CSS</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Styling_basics/Fundamental_CSS_comprehension\">Comprensión de los fundamentos de CSS</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Styling_basics/Fancy_letterheaded_paper\" class=\"only-in-en-us\">Challenge: Creating fancy letterheaded paper</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Styling_basics/Cool-looking_box\" class=\"only-in-en-us\">Challenge: A cool-looking box</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Core/Text_styling\">CSS text styling</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Core/Text_styling/Fundamentals\">Fundamentos de texto y fuentes tipográficas</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Text_styling/Styling_lists\">Aplicación de estilo a listas</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Text_styling/Styling_links\">Dar estilo a los enlaces</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Text_styling/Web_fonts\">Fuentes web</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Text_styling/Typesetting_a_homepage\" class=\"only-in-en-us\">Challenge: Typesetting a community school homepage</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Core/CSS_layout\">CSS layout</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Core/CSS_layout/Introduction\">Introducción al diseño en CSS</a></li><li><a href=\"/es/docs/Learn_web_development/Core/CSS_layout/Floats\">Floats</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/CSS_layout/Positioning\" class=\"only-in-en-us\">Positioning</a></li><li><a href=\"/es/docs/Learn_web_development/Core/CSS_layout/Flexbox\">Flexbox</a></li><li><a href=\"/es/docs/Learn_web_development/Core/CSS_layout/Grids\">Cuadrículas</a></li><li><a href=\"/es/docs/Learn_web_development/Core/CSS_layout/Responsive_Design\">Diseño receptivo</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/CSS_layout/Media_queries\" class=\"only-in-en-us\">Media query fundamentals</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/CSS_layout/Fundamental_Layout_Comprehension\" class=\"only-in-en-us\">Challenge: Fundamental layout comprehension</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Core/Scripting\">Dynamic scripting with JavaScript</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/What_is_JavaScript\">¿Qué es JavaScript?</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/A_first_splash\">Un primer acercamiento a JavaScript</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/What_went_wrong\">¿Qué ha salido mal? Corrigiendo JavaScript</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/Variables\">Almacenando la información que necesitas - Variables</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/Math\">Matemáticas básicas en JavaScript — números y operadores</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/Strings\">Manejar texto — cadenas en JavaScript</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/Useful_string_methods\">Métodos útiles con cadenas</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/Arrays\">Arreglos</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/Silly_story_generator\">Generador de historias absurdas</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/Conditionals\">Tomando decisiones en tu código — condicionales</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/Loops\">Código de bucle</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Scripting/Functions\" class=\"only-in-en-us\">Functions — reusable blocks of code</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/Build_your_own_function\">Construye tu propia función</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/Return_values\">Una función retorna valores</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/Events\">Introducción a los eventos</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Scripting/Event_bubbling\" class=\"only-in-en-us\">Event bubbling</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Scripting/Image_gallery\" class=\"only-in-en-us\">Challenge: Image gallery</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/Object_basics\">Conceptos básicos de los objetos JavaScript</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Scripting/DOM_scripting\" class=\"only-in-en-us\">DOM scripting introduction</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/Network_requests\">AJAX</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Scripting/JSON\">Trabajando con JSON</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Scripting/Debugging_JavaScript\" class=\"only-in-en-us\">Debugging JavaScript and handling errors</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Core/Frameworks_libraries\">JavaScript frameworks and libraries</a></summary><ol><li><a href=\"/en-US/docs/Learn_web_development/Core/Frameworks_libraries/Introduction\" class=\"only-in-en-us\">Introduction to client-side frameworks</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Frameworks_libraries/Main_features\" class=\"only-in-en-us\">Framework main features</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Frameworks_libraries/React_getting_started\">Primeros pasos en React</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Frameworks_libraries/React_todo_list_beginning\" class=\"only-in-en-us\">Beginning our React todo list</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Frameworks_libraries/React_components\">Creando componentes en nuestra app de React</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Frameworks_libraries/React_interactivity_events_state\" class=\"only-in-en-us\">React interactivity: Events and state</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Frameworks_libraries/React_interactivity_filtering_conditional_rendering\" class=\"only-in-en-us\">React interactivity: Editing, filtering, conditional rendering</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Frameworks_libraries/React_accessibility\" class=\"only-in-en-us\">Accessibility in React</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Frameworks_libraries/React_resources\" class=\"only-in-en-us\">React resources</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Core/Accessibility\">Accessibility</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Core/Accessibility/What_is_accessibility\">¿Qué es la accesibilidad?</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Accessibility/Tooling\" class=\"only-in-en-us\">Accessibility tooling and assistive technology</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Accessibility/HTML\">HTML: Una buena base para la accesibilidad</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Accessibility/CSS_and_JavaScript\">Buenas prácticas de accesibilidad CSS y JavaScript</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Accessibility/WAI-ARIA_basics\" class=\"only-in-en-us\">WAI-ARIA basics</a></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Accessibility/Multimedia\" class=\"only-in-en-us\">Accessible multimedia</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Accessibility/Mobile\">Mobile accessibility</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Accessibility/Accessibility_troubleshooting\">Evaluación: Solución de problemas de accesibilidad</a></li></ol></details></li><li><a href=\"/en-US/docs/Learn_web_development/Core/Design_for_developers\" class=\"only-in-en-us\">Design for developers</a></li><li><a href=\"/es/docs/Learn_web_development/Core/Version_control\">Version control</a></li><li class=\"section\"><a href=\"/en-US/docs/Learn_web_development/Extensions\" class=\"only-in-en-us\">Extension modules</a></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects\">Advanced JavaScript objects</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects/Object_prototypes\">Prototipos de objetos</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects/Object-oriented_programming\" class=\"only-in-en-us\">Object-oriented programming</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects/Classes_in_JavaScript\">Clases en JavaScript</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects/Object_building_practice\">Ejercicio práctico de construcción de objetos</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects/Adding_bouncing_balls_features\">Añadiendo características a nuestra demo de bouncing balls</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Extensions/Client-side_APIs\">Client-side web APIs</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Extensions/Client-side_APIs/Introduction\">Introducción a las APIs web</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Client-side_APIs/Video_and_audio_APIs\" class=\"only-in-en-us\">Video and Audio APIs</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Client-side_APIs/Drawing_graphics\" class=\"only-in-en-us\">Drawing graphics</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Client-side_APIs/Client-side_storage\">Almacenamiento del lado cliente</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Client-side_APIs/Third_party_APIs\" class=\"only-in-en-us\">Third-party APIs</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Extensions/Async_JS\">Asynchronous JavaScript</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Extensions/Async_JS/Introducing\">Introducción a JavaScript asíncrono</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Async_JS/Promises\" class=\"only-in-en-us\">How to use promises</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Async_JS/Implementing_a_promise-based_API\" class=\"only-in-en-us\">How to implement a promise-based API</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Async_JS/Introducing_workers\" class=\"only-in-en-us\">Introducing workers</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Async_JS/Sequencing_animations\" class=\"only-in-en-us\">Challenge: Sequencing animations</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Extensions/Forms\">Web forms</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Extensions/Forms/Your_first_form\">Mi primer formulario HTML</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Forms/How_to_structure_a_web_form\">Cómo estructurar un formulario HTML</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Forms/Basic_native_form_controls\">Controles de formulario originales</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Forms/HTML5_input_types\">Tipos de input de HTML5</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Forms/Other_form_controls\" class=\"only-in-en-us\">Other form controls</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Forms/Styling_web_forms\">Estilizando formularios HTML</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Forms/Advanced_form_styling\" class=\"only-in-en-us\">Advanced form styling</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Forms/UI_pseudo-classes\" class=\"only-in-en-us\">UI pseudo-classes</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Forms/Form_validation\">Validación de formularios de datos</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Forms/Sending_and_retrieving_form_data\">Sending form data</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Extensions/Client-side_tools\">Understanding client-side tools</a></summary><ol><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Client-side_tools/Overview\" class=\"only-in-en-us\">Client-side tooling overview</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Client-side_tools/Package_management\" class=\"only-in-en-us\">Package management basics</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Client-side_tools/Introducing_complete_toolchain\" class=\"only-in-en-us\">Introducing a complete toolchain</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Client-side_tools/Deployment\" class=\"only-in-en-us\">Deploying our app</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Extensions/Server-side\">Server-side websites</a></summary><ol><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/First_steps\">Server-side first steps</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/First_steps/Introduction\">Introducción al lado servidor</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/First_steps/Client-Server_overview\">Visión General Cliente-Servidor</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/First_steps/Web_frameworks\">Frameworks Web de lado servidor</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/First_steps/Website_security\">Seguridad de Sitios Web</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Django\">Django web framework (Python)</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Django/Introduction\">Introducción a Django</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Django/development_environment\">Puesta en marcha de un entorno de desarrollo Django</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Django/Tutorial_local_library_website\">Tutorial Django: El Sitio Web de La Biblioteca Local</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Django/skeleton_website\">Tutorial Django Parte 2: Creación del esqueleto del sitio web</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Django/Models\">Tutorial Django Parte 3: Uso de modelos</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Django/Admin_site\">Tutorial Django Parte 4: Sitio de Administración de Django</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Django/Home_page\">Tutorial de Django Parte 5: Creación de tu página de inicio</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Django/Generic_views\">Tutorial de Django Parte 6: Listas genéricas y vistas de detalle</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Django/Sessions\">Tutorial de Django Parte 7: Framework de sesiones</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Django/Authentication\">Tutorial de Django Parte 8: Autenticación y permisos de Usuario</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Django/Forms\">Tutorial de Django Parte 9: Trabajo con formularios</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Django/Testing\">Tutorial de Django Parte 10: Probando una aplicación web Django</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Django/Deployment\">Tutorial de Django Parte 11: Desplegando Django a producción</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Server-side/Django/web_application_security\" class=\"only-in-en-us\">Django web application security</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Server-side/Django/django_assessment_blog\" class=\"only-in-en-us\">Assessment: DIY Django mini blog</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs\">Express web framework (Node.js)</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Introduction\">Introducción a Express/Node</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/development_environment\">Setting up a Node development environment</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Tutorial_local_library_website\">Express Tutorial: El sitio web de la Biblioteca Local</a></li><li><a href=\"/es/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/skeleton_website\">Express Tutorial Part 2: Creating a skeleton website</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/mongoose\" class=\"only-in-en-us\">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/routes\" class=\"only-in-en-us\">Express Tutorial Part 4: Routes and controllers</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Displaying_data\" class=\"only-in-en-us\">Express Tutorial Part 5: Displaying library data</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/forms\" class=\"only-in-en-us\">Express Tutorial Part 6: Working with forms</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/deployment\" class=\"only-in-en-us\">Express Tutorial Part 7: Deploying to production</a></li></ol></details></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Extensions/Performance\">Web performance</a></summary><ol><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Performance/why_web_performance\" class=\"only-in-en-us\">The \"why\" of web performance</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Performance/What_is_web_performance\" class=\"only-in-en-us\">What is web performance?</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Performance/Perceived_performance\" class=\"only-in-en-us\">Perceived performance</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Performance/Measuring_performance\" class=\"only-in-en-us\">Measuring performance</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Performance/Multimedia\" class=\"only-in-en-us\">Multimedia: Images</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Performance/video\" class=\"only-in-en-us\">Multimedia: video</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Performance/JavaScript\" class=\"only-in-en-us\">JavaScript performance optimization</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Performance/HTML\" class=\"only-in-en-us\">HTML performance optimization</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Performance/CSS\" class=\"only-in-en-us\">CSS performance optimization</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Performance/business_case_for_performance\" class=\"only-in-en-us\">The business case for web performance</a></li></ol></details></li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Extensions/Testing\">Testing</a></summary><ol><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Testing/Introduction\" class=\"only-in-en-us\">Introduction to cross-browser testing</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Testing/Testing_strategies\" class=\"only-in-en-us\">Strategies for carrying out testing</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Testing/HTML_and_CSS\" class=\"only-in-en-us\">Handling common HTML and CSS problems</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Testing/Feature_detection\" class=\"only-in-en-us\">Implementing feature detection</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Testing/Automated_testing\" class=\"only-in-en-us\">Introduction to automated testing</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Testing/Your_own_automation_environment\" class=\"only-in-en-us\">Setting up your own test automation environment</a></li></ol></details></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Transform_animate\" class=\"only-in-en-us\">Transform and animate CSS</a></li><li><a href=\"/en-US/docs/Learn_web_development/Extensions/Security_privacy\" class=\"only-in-en-us\">Security and privacy</a></li><li class=\"section\">Further resources</li><li class=\"toggle\"><details><summary><a href=\"/es/docs/Learn_web_development/Howto\">How to solve common problems</a></summary><ol><li><a href=\"/es/docs/Learn_web_development/Howto/Solve_HTML_problems\">Solución de problemas comunes de HTML</a></li><li><a href=\"/es/docs/Learn_web_development/Howto/Solve_CSS_problems\">Usa CSS para resolver problemas comunes</a></li><li><a href=\"/es/docs/Learn_web_development/Howto/Solve_JavaScript_problems\">Resuelva problemas comunes en su código JavaScript</a></li><li><a href=\"/en-US/docs/Learn_web_development/Howto/Web_mechanics\" class=\"only-in-en-us\">Web mechanics</a></li><li><a href=\"/en-US/docs/Learn_web_development/Howto/Tools_and_setup\" class=\"only-in-en-us\">Tools and setup</a></li><li><a href=\"/en-US/docs/Learn_web_development/Howto/Design_and_accessibility\" class=\"only-in-en-us\">Design and accessibility</a></li></ol></details></li><li><a href=\"/en-US/docs/Learn_web_development/About\" class=\"only-in-en-us\">About</a></li><li><a href=\"/en-US/docs/Learn_web_development/Educators\" class=\"only-in-en-us\">Resources for educators</a></li><li><a href=\"/en-US/docs/Learn_web_development/Changelog\" class=\"only-in-en-us\">Changelog</a></li></ol>","source":{"folder":"es/learn_web_development/extensions/forms/how_to_build_custom_form_controls","github_url":"https://github.com/mdn/translated-content/blob/main/files/es/learn_web_development/extensions/forms/how_to_build_custom_form_controls/index.md","last_commit_url":"https://github.com/mdn/translated-content/commit/82f53f6ffe2654ab442fb8d4ceaee4211ca90f7f","filename":"index.md"},"summary":"page(Doc) not found /es/docs/Learn/HTML/Forms/Form_validation","title":"Cómo crear widgets de formularios personalizados","toc":[{"text":"Diseño, estructura, y semántica","id":"diseño_estructura_y_semántica"},{"text":"Bring your widget to life with JavaScript","id":"bring_your_widget_to_life_with_javascript"},{"text":"Make it accessible","id":"make_it_accessible"},{"text":"Conclusion","id":"conclusion"}],"pageType":"learn-module-chapter"}}</script></body></html>

Pages: 1 2 3 4 5 6 7 8 9 10