A place to cache linked articles (think custom and personal wayback machine)
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>On Designing and Building Toggle Switches (archive) — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <meta name="robots" content="noindex, nofollow">
  28. <meta content="origin-when-cross-origin" name="referrer">
  29. <!-- Canonical URL for SEO purposes -->
  30. <link rel="canonical" href="https://www.sarasoueidan.com/blog/toggle-switch-design/">
  31. <style>
  32. /* http://meyerweb.com/eric/tools/css/reset/ */
  33. html, body, div, span,
  34. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  35. a, abbr, address, big, cite, code,
  36. del, dfn, em, img, ins,
  37. small, strike, strong, tt, var,
  38. dl, dt, dd, ol, ul, li,
  39. fieldset, form, label, legend,
  40. table, caption, tbody, tfoot, thead, tr, th, td,
  41. article, aside, canvas, details, embed,
  42. figure, figcaption, footer, header, hgroup,
  43. menu, nav, output, ruby, section, summary,
  44. time, mark, audio, video {
  45. margin: 0;
  46. padding: 0;
  47. border: 0;
  48. font-size: 100%;
  49. font: inherit;
  50. vertical-align: baseline;
  51. }
  52. /* HTML5 display-role reset for older browsers */
  53. article, aside, details, figcaption, figure,
  54. footer, header, hgroup, menu, nav, section { display: block; }
  55. body { line-height: 1; }
  56. blockquote, q { quotes: none; }
  57. blockquote:before, blockquote:after,
  58. q:before, q:after {
  59. content: '';
  60. content: none;
  61. }
  62. table {
  63. border-collapse: collapse;
  64. border-spacing: 0;
  65. }
  66. /* http://practicaltypography.com/equity.html */
  67. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  68. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  69. @font-face {
  70. font-family: 'EquityTextB';
  71. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  72. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  73. font-weight: 300;
  74. font-style: normal;
  75. font-display: swap;
  76. }
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: italic;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  89. font-weight: 700;
  90. font-style: normal;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'ConcourseT3';
  95. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  96. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  97. font-weight: 300;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  102. body {
  103. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  104. font-family: "EquityTextB", Palatino, serif;
  105. background-color: #f0f0ea;
  106. color: #07486c;
  107. font-kerning: normal;
  108. -moz-osx-font-smoothing: grayscale;
  109. -webkit-font-smoothing: subpixel-antialiased;
  110. text-rendering: optimizeLegibility;
  111. font-variant-ligatures: common-ligatures contextual;
  112. font-feature-settings: "kern", "liga", "clig", "calt";
  113. }
  114. pre, code, kbd, samp, var, tt {
  115. font-family: 'TriplicateT4c', monospace;
  116. }
  117. em {
  118. font-style: italic;
  119. color: #323a45;
  120. }
  121. strong {
  122. font-weight: bold;
  123. color: black;
  124. }
  125. nav {
  126. background-color: #323a45;
  127. color: #f0f0ea;
  128. display: flex;
  129. justify-content: space-around;
  130. padding: 1rem .5rem;
  131. }
  132. nav:last-child {
  133. border-bottom: 1vh solid #2d7474;
  134. }
  135. nav a {
  136. color: #f0f0ea;
  137. }
  138. nav abbr {
  139. border-bottom: 1px dotted white;
  140. }
  141. h1 {
  142. border-top: 1vh solid #2d7474;
  143. border-bottom: .2vh dotted #2d7474;
  144. background-color: #e3e1e1;
  145. color: #323a45;
  146. text-align: center;
  147. padding: 5rem 0 4rem 0;
  148. width: 100%;
  149. font-family: 'ConcourseT3';
  150. display: flex;
  151. flex-direction: column;
  152. }
  153. h1.single {
  154. padding-bottom: 10rem;
  155. }
  156. h1 span {
  157. position: absolute;
  158. top: 1vh;
  159. left: 20%;
  160. line-height: 0;
  161. }
  162. h1 span a {
  163. line-height: 1.7;
  164. padding: 1rem 1.2rem .6rem 1.2rem;
  165. border-radius: 0 0 6% 6%;
  166. background: #2d7474;
  167. font-size: 1.3rem;
  168. color: white;
  169. text-decoration: none;
  170. }
  171. h2 {
  172. margin: 4rem 0 1rem;
  173. border-top: .2vh solid #2d7474;
  174. padding-top: 1vh;
  175. }
  176. h3 {
  177. text-align: center;
  178. margin: 3rem 0 .75em;
  179. }
  180. hr {
  181. height: .4rem;
  182. width: .4rem;
  183. border-radius: .4rem;
  184. background: #07486c;
  185. margin: 2.5rem auto;
  186. }
  187. time {
  188. display: bloc;
  189. margin-left: 0 !important;
  190. }
  191. ul, ol {
  192. margin: 2rem;
  193. }
  194. ul {
  195. list-style-type: square;
  196. }
  197. a {
  198. text-decoration-skip-ink: auto;
  199. text-decoration-thickness: 0.05em;
  200. text-underline-offset: 0.09em;
  201. }
  202. article {
  203. max-width: 50rem;
  204. display: flex;
  205. flex-direction: column;
  206. margin: 2rem auto;
  207. }
  208. article.single {
  209. border-top: .2vh dotted #2d7474;
  210. margin: -6rem auto 1rem auto;
  211. background: #f0f0ea;
  212. padding: 2rem;
  213. }
  214. article p:last-child {
  215. margin-bottom: 1rem;
  216. }
  217. p {
  218. padding: 0 .5rem;
  219. margin-left: 3rem;
  220. }
  221. p + p,
  222. figure + p {
  223. margin-top: 2rem;
  224. }
  225. blockquote {
  226. background-color: #e3e1e1;
  227. border-left: .5vw solid #2d7474;
  228. display: flex;
  229. flex-direction: column;
  230. align-items: center;
  231. padding: 1rem;
  232. margin: 1.5rem;
  233. }
  234. blockquote cite {
  235. font-style: italic;
  236. }
  237. blockquote p {
  238. margin-left: 0;
  239. }
  240. figure {
  241. border-top: .2vh solid #2d7474;
  242. background-color: #e3e1e1;
  243. text-align: center;
  244. padding: 1.5rem 0;
  245. margin: 1rem 0 0;
  246. font-size: 1.5rem;
  247. width: 100%;
  248. }
  249. figure img {
  250. max-width: 250px;
  251. max-height: 250px;
  252. border: .5vw solid #323a45;
  253. padding: 1px;
  254. }
  255. figcaption {
  256. padding: 1rem;
  257. line-height: 1.4;
  258. }
  259. aside {
  260. display: flex;
  261. flex-direction: column;
  262. background-color: #e3e1e1;
  263. padding: 1rem 0;
  264. border-bottom: .2vh solid #07486c;
  265. }
  266. aside p {
  267. max-width: 50rem;
  268. margin: 0 auto;
  269. }
  270. /* https://fvsch.com/code/css-locks/ */
  271. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  272. font-size: 1rem;
  273. line-height: calc( 1.5em + 0.2 * 1rem );
  274. }
  275. h1 {
  276. font-size: 1.9rem;
  277. line-height: calc( 1.2em + 0.2 * 1rem );
  278. }
  279. h2 {
  280. font-size: 1.6rem;
  281. line-height: calc( 1.3em + 0.2 * 1rem );
  282. }
  283. h3 {
  284. font-size: 1.35rem;
  285. line-height: calc( 1.4em + 0.2 * 1rem );
  286. }
  287. @media (min-width: 20em) {
  288. /* The (100vw - 20rem) / (50 - 20) part
  289. resolves to 0-1rem, depending on the
  290. viewport width (between 20em and 50em). */
  291. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  292. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  293. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  294. margin-left: 0;
  295. }
  296. h1 {
  297. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  298. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  299. }
  300. h2 {
  301. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  302. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  303. }
  304. h3 {
  305. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. }
  309. @media (min-width: 50em) {
  310. /* The right part of the addition *must* be a
  311. rem value. In this example we *could* change
  312. the whole declaration to font-size:2.5rem,
  313. but if our baseline value was not expressed
  314. in rem we would have to use calc. */
  315. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  316. font-size: calc( 1rem + .6 * 1rem );
  317. line-height: 1.5em;
  318. }
  319. p, li, pre, details {
  320. margin-left: 3rem;
  321. }
  322. h1 {
  323. font-size: calc( 1.9rem + 1.5 * 1rem );
  324. line-height: 1.2em;
  325. }
  326. h2 {
  327. font-size: calc( 1.5rem + 1.5 * 1rem );
  328. line-height: 1.3em;
  329. }
  330. h3 {
  331. font-size: calc( 1.35rem + 1.5 * 1rem );
  332. line-height: 1.4em;
  333. }
  334. figure img {
  335. max-width: 500px;
  336. max-height: 500px;
  337. }
  338. }
  339. figure.unsquared {
  340. margin-bottom: 1.5rem;
  341. }
  342. figure.unsquared img {
  343. height: inherit;
  344. }
  345. @media print {
  346. body { font-size: 100%; }
  347. a:after { content: " (" attr(href) ")"; }
  348. a, a:link, a:visited, a:after {
  349. text-decoration: underline;
  350. text-shadow: none !important;
  351. background-image: none !important;
  352. background: white;
  353. color: black;
  354. }
  355. abbr[title] { border-bottom: 0; }
  356. abbr[title]:after { content: " (" attr(title) ")"; }
  357. img { page-break-inside: avoid; }
  358. @page { margin: 2cm .5cm; }
  359. h1, h2, h3 { page-break-after: avoid; }
  360. p3 { orphans: 3; widows: 3; }
  361. img {
  362. max-width: 250px !important;
  363. max-height: 250px !important;
  364. }
  365. nav, aside { display: none; }
  366. }
  367. ul.with_columns {
  368. column-count: 1;
  369. }
  370. @media (min-width: 20em) {
  371. ul.with_columns {
  372. column-count: 2;
  373. }
  374. }
  375. @media (min-width: 50em) {
  376. ul.with_columns {
  377. column-count: 3;
  378. }
  379. }
  380. ul.with_two_columns {
  381. column-count: 1;
  382. }
  383. @media (min-width: 20em) {
  384. ul.with_two_columns {
  385. column-count: 1;
  386. }
  387. }
  388. @media (min-width: 50em) {
  389. ul.with_two_columns {
  390. column-count: 2;
  391. }
  392. }
  393. .gallery {
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. }
  398. .gallery figure img {
  399. margin-left: 1rem;
  400. margin-right: 1rem;
  401. }
  402. .gallery figure figcaption {
  403. font-family: 'ConcourseT3'
  404. }
  405. footer {
  406. font-family: 'ConcourseT3';
  407. display: flex;
  408. flex-direction: column;
  409. border-top: 3px solid white;
  410. padding: 4rem 0;
  411. background-color: #07486c;
  412. color: white;
  413. }
  414. footer > * {
  415. max-width: 50rem;
  416. margin: 0 auto;
  417. }
  418. footer a {
  419. color: #f1c40f;
  420. }
  421. footer .avatar {
  422. width: 200px;
  423. height: 200px;
  424. border-radius: 50%;
  425. float: left;
  426. -webkit-shape-outside: circle();
  427. shape-outside: circle();
  428. margin-right: 2rem;
  429. padding: 2px 5px 5px 2px;
  430. background: white;
  431. border-left: 1px solid #f1c40f;
  432. border-top: 1px solid #f1c40f;
  433. border-right: 5px solid #f1c40f;
  434. border-bottom: 5px solid #f1c40f;
  435. }
  436. </style>
  437. <h1>
  438. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  439. On Designing and Building Toggle Switches (archive)
  440. <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
  441. </h1>
  442. <section>
  443. <article>
  444. <h3><a href="https://www.sarasoueidan.com/blog/toggle-switch-design/">Source originale du contenu</a></h3>
  445. <p class="size-2x">Yesterday I was working on creating the slides and accompanying demos for my upcoming <a href="http://www.webdirections.org/code/speakers/sara-soueidan.php">Web Directions Code talk</a> next week. One of the demos I’m creating is a basic proof of concept for a simple switch that is used to switch the theme of a UI from light to dark and vice versa. I liked, and was inspired, by the theme switch in the Medium app, shown below.</p>
  446. <figure>
  447. <img src="https://d33wubrfki0l68.cloudfront.net/50365e8dc1b4c61835ec8be3b0a6a7cc45fabbf7/b3c46/images/medium-theme-switch.jpg" alt="Screenshot of the Medium Theme Switcher"/>
  448. <figcaption>
  449. The Medium app’s theme customizer is a simple popup panel that includes a simple switch for switching from light to dark mode and vice versa.
  450. </figcaption>
  451. </figure>
  452. <p>The only difference is that I wanted my switch to explicitly state which theme is currently enabled, so instead of just enabling and disabling a dark theme like the Medium switch, I wanted the user to explicitly switch between Light and Dark options. There’s no particular reason for that other than personal preference. There are <a href="https://inclusive-components.design/a-theme-switcher/">other ways</a> to do this as well. How you design it is a personal preference, as long as it works and is easily understandable by users.</p>
  453. <p>As always, I started thinking about how to mark this simple element up, ensuring accessibility is baked right into it from the start. So I started doing my homework and reading and learning all I can about this topic.</p>
  454. <p>It was important for me to make sure this demo is accessible even if it’s just a quick proof of concept for a talk. First of all, because the code for the demo will be public, so I have a bigger responsibility for making sure it’s accessible, because I wouldn’t want to spread any inaccessible code around, especially if there’s a chance people might be using it somewhere else.</p>
  455. <p>Another reason I wanted this to be good is that I’ll probably want to reuse it for other components for my upcoming <a href="../../workshops/universal-components/">front-end components workshop</a>.</p>
  456. <h2 id="start-with-the-markup">Start with the markup</h2>
  457. <p>As I mentioned above, I started thinking about how to mark this element up ensuring it is accessible to screen readers. That’s when I realized (and it was a “D’oh!” kinda moment) that function and markup depend on how I want the toggle to behave, <em>and</em> on how I want it to look.</p>
  458. <p>It was pretty clear to me: the switch would allow a user to choose between a light theme and a dark theme, with the light theme being the default. It was at this moment that radio buttons came to my mind: two options with one of them checked, and only one option can be checked at a time; that makes a great use case for good old radio buttons.</p>
  459. <p>I knew I would have to choose something different if I wanted the UI to look and behave differently. For example, if I wanted the UI to say “Enable/Disable Dark Mode”, then I wouldn’t need or want to use radio buttons, because I’d only have one option to deal with that could be <strong>switched on and off</strong> — <em>that</em> would be a great use case for a checkbox or a good old toggle <code>&lt;button&gt;</code>.</p>
  460. <p><strong>Takeaways:</strong></p>
  461. <ul>
  462. <li>Style and function are interrelated; so it helps to think about these two simultaneously when designing for accessibility (and designing anything in general, really).</li>
  463. <li><strong>Always, always start thinking about the markup and accessibility when building components</strong>, regardless of how small or simple they seem.</li>
  464. </ul>
  465. <h2 id="research">Research</h2>
  466. <p>As always, I needed to back my theory and practice up with good research. So I started reading. My first go-to references are Heydon Pickering’s <a href="https://inclusive-components.design/">Inclusive Components</a> and <a href="https://a11yproject.com/">The A11y Project</a>.</p>
  467. <p>As it turns out, Heydon had <a href="https://inclusive-components.design/toggle-button/">a fantastic article just about Toggle Buttons</a> which I learned a lot from, and my friend <a href="https://www.scottohara.me/">Scott O’Hara</a> had <a href="https://scottaohara.github.io/aria-switch-button/">an ARIA switch button</a> included in <a href="https://a11yproject.com/patterns">the Patterns section</a> of the A11y Project. So, naturally, I inspected the code for that button and read Heydon’s article to confirm if I’m on the right path. Thankfully, it turns out I was.</p>
  468. <p><em>Before I move on it’s worth mentioning that this is <strong>not</strong> an article about how to create accessible toggle switches. Heydon’s article does a fantastic job covering that.</em></p>
  469. <p>The main points I personally concluded from the above research are:</p>
  470. <ol>
  471. <li>There are different types of switches that seem to do similar things but are fundamentally different when it comes to markup and accessibility. Just because they are styled to look the same, doesn’t mean they <em>are</em> necessarily the same.</li>
  472. <li>I need to think about how I want the UI to behave, look <em>and sound</em> when marking the switch up. Design and UX first, then code.</li>
  473. <li><strong>A toggle switch can be used to switch between two separate options, or it can be used to switch one option on and off (or like enabling/disabling an option).</strong> This is where the implementation differences start to manifest.</li>
  474. <li>If you’re switching between two separate options, using standard radio buttons makes sense. <strong>Radio buttons are used when you need your users to choose one of two or more options, so this is a perfect use case for them.</strong> They also have basic accessibility and keyboard tabbing baked right in. Just make sure you don’t break the accessibility of either of these in the HTML or using CSS.</li>
  475. <li><p>If the purpose of the switch is to enable/disable a feature (or turn it on/off), there are other approaches for it:</p>
  476. <ul>
  477. <li>Using a checkbox to check/uncheck (enable/disable) that option.</li>
  478. <li>Using a <code>&lt;button&gt;</code> that can have two states: pressed and not pressed. This approach requires the use of ARIA which in turn will require JavaScript to function properly for assistive technologies (the ARIA attribute values are updated on click with JavaScript). Read Heydon’t article for more details on this approach.</li>
  479. <li>This approach means that you have only one option, which in turn means that the switch button has only one associated label, and would probably not look like a “double-switch” button anymore, <em>unless</em>:
  480. <ul>
  481. <li>The double-switch button (indicating on/off) states would have clear text indicating what the current active state is; so it would look like this example from Scott’s demo mentioned in the A11y project : <img src="https://d33wubrfki0l68.cloudfront.net/59a0b5c22479918de19f40f10076f2df220fef99/a394d/images/on-off-switch.png" alt="the On/OFF switch pattern by Scott O’Hara"/></li>
  482. </ul></li>
  483. </ul></li>
  484. </ol>
  485. <p>For my use theme switcher, it was a clear choice: since the user has two options (dark and light), I was going to use radio buttons. <strong>I didn’t want it to say “turn dark mode on/off” — I wanted it to say “Enable light mode or enable dark mode”.</strong> The solution will be CSS and HTML only, require no JavaScript, and accessibility would be baked right in by default.</p>
  486. <p><strong>Takeaway:</strong></p>
  487. <ul>
  488. <li>Have a clear vision of what the design of the component is supposed to do and not do. Base your markup and styles on that.</li>
  489. </ul>
  490. <div class="note update">
  491. <p>
  492. If you like this article you may love my Universal Components Workshop. I’m running this workshop at a couple of events in the Fall, so you may like to sign up for one of them.
  493. </p>
  494. <p>
  495. I also run in-house workshops, so you can request this workshop (or another one) for your team.
  496. </p>
  497. <a href="../../workshops/">Learn More</a>
  498. </div>
  499. <h2 id="inspecting-code">Inspecting Code</h2>
  500. <p>Now, after reading and inspecting in the above resources, I decided to see how other people are marking such a switch up. After all, I knew I’d find tonnes of examples of toggle switches that look and behave like this visually on <a href="https://codepen.io/">Codepen</a>, and it’s always great to see how others are doing it — maybe I’m missing on some cool techniques for styling that I can learn, or maybe I’m missing some important piece of information when it comes to the markup and accessibility.</p>
  501. <p>Since I was working <em>in</em> Codepen on my own version of this switch (demo shared below), I thought I’d inspect Codepen’s own toggle switch: the switch that you use to choose whether you want the pen to be public or private.</p>
  502. <p><strong>Quick takeaway:</strong></p>
  503. <ul>
  504. <li>It is always helpful and interesting to learn from other people’s work by inspecting their code (be that via the devtools or on Codepen).</li>
  505. </ul>
  506. <h2 id="the-codepen-privacy-switch">The Codepen Privacy Switch</h2>
  507. <p>The Codepen switch exhibited some weird behavior that I had also noticed before but never felt curious enough to “debug”. The following video shows this behavior:</p>
  508. <video controls="">
  509. <source src="/../../videos/codepen-switch-bug.mp4" type="video/mp4">
  510. Your browser does not support the video tag.
  511. </source></video>
  512. <p>First, it’s worth mentioning that I was always confused by this switch. The red color vs green always gave out a weird feedback loop. If I make the pen private, the color turns green, and making it public makes it red. In my head, green meant more like “this pen is accessible by people, it’s open” and red was more like “this pen is closed, people are NOT allowed access”. But in Codepen terms, the colors symbolized other things. More specifically, the red stands for “PUBLIC ZONE = DANGER ZONE” or “BEWARE this pen is now PUBLIC which is NOT GOOD”. (Sorry, I’m being too dramatic, but purposefully.)</p>
  513. <p>Then there was the buggy behavior demonstrated in the video above (where clicking on the same label changes the value of the switch to the other label’s value). I was curious to see what was causing it so, again, I fired up devtools and inspected the code.</p>
  514. <figure>
  515. <img src="https://d33wubrfki0l68.cloudfront.net/1299c34b8d04ceb6a187c641e05167e2bf4b1f06/8f5a7/images/codepen-switch-code.png" alt="Screenshot of the devtools inspecting code for the Codepen switch"/>
  516. <figcaption>
  517. The Codepen switch is marked up as a checkbox that has two labels.
  518. </figcaption>
  519. </figure>
  520. <p>I found that the Codepen switch is one checkbox that appears to have two labels. That’s why clicking on both “Public” and “Private” multiple times would cause the switch to be turned on and off. In other words, this is why the visual feedback/behavior of the switch did not match the purpose of the switch or what it seemed to do.</p>
  521. <p>I <a href="https://twitter.com/SaraSoueidan/status/1021068702110887937">tweeted about this bug</a>, <a href="https://twitter.com/SaraSoueidan/status/1021068905652121600">its possible cause</a>, and <a href="https://twitter.com/SaraSoueidan/status/1021069590628044800">suggested an alternative solution</a> that could fix it.</p>
  522. <p>I started getting responses and opinions from other developers, which led to a very good and enlightening discussion that I thoroughly enjoyed (one of the few positive aspects of Twitter!).</p>
  523. <p>The general consensus was that the switch needed fixing, of course. But how it would be fixed depends largely on what the Codepen team wants it to do:</p>
  524. <h3 id="if-the-switch-toggle-is-supposed-to-be-an-enable-disable-button-for-the-private-option">If the switch toggle is supposed to be an enable/disable button for the “Private” option</h3>
  525. <p><strong>Then:</strong></p>
  526. <ul>
  527. <li>That would mean that the ON/OFF switch pattern mentioned in the previous section above would be the ideal solution.</li>
  528. <li>This means that the “Public” label would be discarded, and only the “Private” label would be preserved, with a clear indication that the switch next to it turns this option on or off. Which brings me to another point:</li>
  529. <li>The visual representation of the switch would either have to change completely or it should be tweaked:</li>
  530. <li>If the switch button were to remain visually the same (i.e. preserve the double-swicth “move this toggle left and right” behavior), I’d suggest adding “On/Off” labels to it akin to what Scott did in his A11y project demo, because according to WCAG, you should not use color alone to convery information. The colors also need to have enough contrast if you were to use them. And, again, I’d reconsider the red and green colors as they may confuse someone else like they confused me for a while. (<a href="https://twitter.com/geare_d/status/1021253054165762048">Someone suggested</a> grey and green, because “The addition of color would more closely connotate the enabled state (no color vs has color as opposed to two separate colors)”.)</li>
  531. <li>The underlying structure and markup for this case (one label only) could be:
  532. <ul>
  533. <li>checkbox, that can be checked or unchecked. I would suggest just using simple (fancy, maybe) checkbox styles for this behavior and ditching the whole double-switch style altogether.</li>
  534. <li><code>&lt;button&gt;</code> with <code>aria-pressed</code> (and <code>aria-checked</code> if needed), where the pressed state would indicate the pen is private, and the unpressed state indicates it’s not private; i.e. it’s public. The style of the button would also change in this case, and the behavior would requir JavaScript to modify the value of the ARIA attributes on click.</li>
  535. </ul></li>
  536. </ul>
  537. <h3 id="if-the-switch-toggle-is-supposed-to-explicitly-offer-and-enable-two-separate-options-public-and-private">If the switch toggle is supposed to explicitly offer and enable two separate options: Public and Private</h3>
  538. <p>Then using a couple of radio buttons would make much sense, in my opinion. Two radio buttons, each with its own label, announced to assisitive technologies as a couple of separate options of which the user should choose one, and perfectly accessible via keyboards, <em>and</em> has no additional ARIA or JS requirements to function. And sprinkle some CSS on it to create the “double-switch” button style if you want (seriously I don’t know what else to call it) and you’re all done. The markup and styles for such a solution might differ slightly between developers, but the essence of the code would be the same.</p>
  539. <p>Which brings me to the live demo…</p>
  540. <h2 id="live-example">Live Example</h2>
  541. <p>This is my implementation of a two-options toggle switch. I created this <em>not</em> as a solution to the Codepen switch, but simply as a switch for my light/dark theme switcher that I’m using in my talk:</p>
  542. <p/><p data-height="300" data-theme-id="3617" data-slug-hash="jpBbrq" data-default-tab="result" data-user="SaraSoueidan" data-embed-version="2" data-pen-title="Accessible Option(/Toggle) Switch" class="codepen">See the Pen <a href="https://codepen.io/SaraSoueidan/pen/jpBbrq/">Accessible Option(/Toggle) Switch</a> by Sara Soueidan (<a href="https://codepen.io/SaraSoueidan">@SaraSoueidan</a>) on <a href="https://codepen.io">CodePen</a>.</p>
  543. <p>My switch is going to live in the context of a larger set of form elements, that’s why I don’t have any fieldset or legend in the demo. And yes I know, I know.. you could also tweak the code to <em>not</em> need the extra <code>span</code>s in the markup and use pseudo-elements instead, but I chose this approach anyway, just because I wanted to. Try to play with the code and uncomment some of the styles (especially try removing the spans to see how my switch looks like without them) to dissect the code and have a better idea of my choices and what I was trying to achieve.</p>
  544. <p>I also added focus styles to ensure keyboard users can see where the focus is since I’ve covered my default radio buttons with the spans, and labels don’t get highlighted on focus by default. I used <code>:focus-visible</code> only at first (with polyfill) but it didn’t work as expected in Firefox and Safari, so I ended up adding <code>:focus</code> back again and using that instead. Also, the focus styles aren’t very pretty, I know, but this demo is just a proof of concept so it doesn’t need to be strikingly beautiful.</p>
  545. <p>If you’d like to see another demo that also uses radio buttons but implements them differently, without spans and using pseudo-elements, check this Codepen by my friend Scott O’hara out:</p>
  546. <p/><p data-height="300" data-theme-id="3617" data-slug-hash="zLZwNv" data-default-tab="result" data-user="scottohara" data-embed-version="2" data-pen-title="Radio Toggle Switch" class="codepen">See the Pen <a href="https://codepen.io/scottohara/pen/zLZwNv/">Radio Toggle Switch</a> by Scott (<a href="https://codepen.io/scottohara">@scottohara</a>) on <a href="https://codepen.io">CodePen</a>.</p>
  547. <h2 id="final-words">Final Words.</h2>
  548. <p>OK let me start by saying that I could be wrong. I know that the Codepen folks may want something completely different, or think of something completely different. So this article is not meant to redesign the Codepen switch button, but rather serve as a documentation of my research and train of thought while working on creating my own switch button for my talk. I’ll need to do even <em>more</em> research when it’s time to continue working on my new workshop, and things may change, and I know I’ll learn and know more by the time I create my next switch. But, until then, I know I’ve got this blog post to reference for some of the thoughts and ideas that crossed my mind when I took the first step into this.</p>
  549. <p>I hope you find something useful reading this article. An if you’ve made it this far already: thank you for reading.</p>
  550. <p>Cheers.</p>
  551. </article>
  552. </section>
  553. <nav id="jumpto">
  554. <p>
  555. <a href="/david/blog/">Accueil du blog</a> |
  556. <a href="https://www.sarasoueidan.com/blog/toggle-switch-design/">Source originale</a> |
  557. <a href="/david/stream/2019/">Accueil du flux</a>
  558. </p>
  559. </nav>
  560. <footer>
  561. <div>
  562. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  563. <p>
  564. Bonjour/Hi!
  565. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  566. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  567. </p>
  568. <p>
  569. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  570. </p>
  571. <p>
  572. Voici quelques articles choisis :
  573. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  574. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  575. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  576. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  577. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  578. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  579. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  580. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  581. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  582. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  583. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  584. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  585. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  586. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  587. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  588. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  589. </p>
  590. <p>
  591. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  592. </p>
  593. <p>
  594. Je ne traque pas ta navigation mais mon
  595. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  596. conserve des logs d’accès.
  597. </p>
  598. </div>
  599. </footer>
  600. <script type="text/javascript">
  601. ;(_ => {
  602. const jumper = document.getElementById('jumper')
  603. jumper.addEventListener('click', e => {
  604. e.preventDefault()
  605. const anchor = e.target.getAttribute('href')
  606. const targetEl = document.getElementById(anchor.substring(1))
  607. targetEl.scrollIntoView({behavior: 'smooth'})
  608. })
  609. })()
  610. </script>