Dialog dilemmas and modal mischief: a deep dive into popovers and how to build them

A presentation at Front Conference in August 2023 in Zürich, Switzerland by Hidde de Vries

Slide 1

Slide 1

t u o b a e r a s r e v o p o p w o h t o l e l o h w a e m o to bec d l i u b o t easier Hidde de Vries 1 September 2023 Front Conference, Zurich

Slide 2

Slide 2

& f e i h c s i Modal m g o l a di s a m m dile Hidde de Vries 1 September 2023 Front Conference, Zurich

Slide 3

Slide 3

@hdv@front-end.social In the beginning, content was linear…

Slide 4

Slide 4

@hdv@front-end.social Today, it can overlap in all sorts of ways

Slide 5

Slide 5

@hdv@front-end.social Polaroids of development of overlapping window management Photos: Bill Atkinson (‘“Mr User Interface” at Apple’), from: Designing interactions by Bill Moggridge, 93.

Slide 6

Slide 6

Slide 7

Slide 7

g o l a i <d HTML element ∙ wide browser support ∙ (as of recently) good accessibility support popover New attribute / API in HTML ∙ supported in Chrome stable / Safari Tech Preview / Firefox (⚑)

Slide 8

Slide 8

g o l a i <d @hdv@front-end.social UI considerations HTML element with wide browser support and (as of recently) good accessibility support Semantics popover Positioning New attribute / API in HTML, v1 supported in Chrome stable, coming to other browsers too

Slide 9

Slide 9

@hdv@front-end.social Hi, I’m Hidde developer relations + accessibility at NL Design System participant at Open UI CG hidde.blog LIKE & SUBSCRIBE

Slide 10

Slide 10

The Economist: a popover to teach how the UI works @hdv@front-end.social

Slide 11

Slide 11

@hdv@front-end.social

Slide 12

Slide 12

Teams: a timed popover to ask for feedback @hdv@front-end.social

Slide 13

Slide 13

@hdv@front-end.social Teams: a popover to teach about an Excel integration

Slide 14

Slide 14

@hdv@front-end.social Teams: a popover to urge me to be my expressive self

Slide 15

Slide 15

@hdv@front-end.social Slack Huddles: a popover to tell the user they look nice today

Slide 16

Slide 16

Online banking: a popover to autocomplete search query @hdv@front-end.social

Slide 17

Slide 17

Online banking: a popover to autocomplete search query Online banking: a popover to select a transfer date @hdv@front-end.social

Slide 18

Slide 18

@hdv@front-end.social dialog What is it Additional window to your main window. a.k.a. “descendant window” or “subwindow”

Slide 19

Slide 19

@hdv@front-end.social dialog What is it Usually contains an action or task for the user, sometimes has critical information

Slide 20

Slide 20

@hdv@front-end.social dialog What is it ‘a “conversation” between the system and the user’ https://carbondesignsystem.com/patterns/dialog-pattern/

Slide 21

Slide 21

@hdv@front-end.social

fi fi dialog What is it (typically) Do you want to continue, yes or no If you want to open a new le, what shall we do with your current le? How do you want to crop this image, where is the hot spot?

Slide 22

Slide 22

@hdv@front-end.social

<dialog> What is it HTML element to build dialogs <dialog>… </dialog> html.spec.whatwg.org/dev/interactive-elements.html#the-dialog-element

Slide 23

Slide 23

@hdv@front-end.social // show as modal element.showModal();

<dialog> Open/close with script // show as non-modal element.show();

Slide 24

Slide 24

@hdv@front-end.social // dialog element <dialog>…</dialog> comes with role, modal setting, closeon-Esc etc

Slide 25

Slide 25

@hdv@front-end.social // dialog element <dialog>…</dialog> just the semantics, no behaviour comes with semantics, top layer, inertness, close-on-Esc etc // an element with // dialog semantics <div role=”dialog”>… </div>

Slide 26

Slide 26

@hdv@front-end.social // dialog element <dialog>…</dialog> just the semantics, no behaviour comes with semantics, top layer, inertness, close-on-Esc etc // an element with // dialog semantics <div role=”dialog”>… </div> “dialog” just the word

Slide 27

Slide 27

@hdv@front-end.social current status: <dialog>

Slide 28

Slide 28

@hdv@front-end.social scottohara.me/blog/2023/01/26/use-the-dialog-element.html more on using <dialog> accessibly

Slide 29

Slide 29

@hdv@front-end.social popover What is it a loating piece of UI with supplemental or contextual content “non-modal dialog” “transient content” f “supplemental” from Dell DS, “contextual” + “non-modal dialog” from Lightning DS, “transient content” from Spectrum

Slide 30

Slide 30

@hdv@front-end.social [popover] What is it an attribute that adds a set of behaviours to any element <div popover>… </div> html.spec.whatwg.org/dev/popover.html#the-popover-attribute

Slide 31

Slide 31

light dismiss keyboard (depending on mode) top layer presence auto-closure of other popovers

Slide 32

Slide 32

@hdv@front-end.social [popover] What is it For elements that are: - on top of other page content - not always visible

(ephemeral, short-lived) usually displayed one at a time html.spec.whatwg.org/dev/popover.html#the-popover-attribute

Slide 33

Slide 33

@hdv@front-end.social https://chromestatus.com/metrics/feature/timeline/popularity/4191

Slide 34

Slide 34

@hdv@front-end.social [popover] Use cases <select>’s listbox content pickers form element suggestions action menus teaching UI

Slide 35

Slide 35

@hdv@front-end.social [popover] No JavaScript required <button> Toggle popover </button> <div> … </div>

Slide 36

Slide 36

@hdv@front-end.social [popover] No JavaScript required <button> Toggle popover </button> <div popover> … </div> make it a popover

Slide 37

Slide 37

@hdv@front-end.social [popover] No JavaScript required <button> Toggle popover </button> <div popover id=”p”> … </div> add a unique ID

Slide 38

Slide 38

@hdv@front-end.social point button to ID [popover] No JavaScript required <button popovertarget=”p”> Toggle popover </button> <div popover id=”p”> … </div>

Slide 39

Slide 39

@hdv@front-end.social opening dialogs (and more) without js github.com/whatwg/html/issues/9625

Slide 40

Slide 40

@hdv@front-end.social [popover] Can open, close or toggle <button popovertarget=”p” popovertargetaction=”show”> Open popover </button> <div popover id=”p”> … </div>

Slide 41

Slide 41

@hdv@front-end.social [popover] Has auto and manual modes // Closes other popovers // when opened; has // light dismiss. <div popover=”auto”> … </div>

Slide 42

Slide 42

@hdv@front-end.social [popover] Has auto and manual modes // Closes other popovers // when opened; has // light dismiss. <div popover=”auto”> … </div> // No closing of others, // no light dismiss <div popover=”manual”> … </div>

Slide 43

Slide 43

@hdv@front-end.social // show as popover element.showPopover(); [popover] Open/close with script

Slide 44

Slide 44

@hdv@front-end.social current status: popover attribute/api in Chrome >114 Tech Preview >167, Safari 17 (fall 2023) Firefox > 114 dom.element.popover.enabled

Slide 45

Slide 45

@hdv@front-end.social 🤔 How are these patterns different?

Slide 46

Slide 46

@hdv@front-end.social modal vs non-modal

Slide 47

Slide 47

The Economist: a modal overlay for privacy consent @hdv@front-end.social

Slide 48

Slide 48

Dutch government: a modal to extend the DigiD session @hdv@front-end.social

Slide 49

Slide 49

A “game over” screen @hdv@front-end.social

Slide 50

Slide 50

The Economist: popover with options @hdv@front-end.social

Slide 51

Slide 51

Social network: a non-modal alternative text dialog @hdv@front-end.social

Slide 52

Slide 52

CMS: a non-modal menu for image options @hdv@front-end.social

Slide 53

Slide 53

Booking website: a non-modal chat widget @hdv@front-end.social

Slide 54

Slide 54

@hdv@front-end.social Making an element modal is a drastic measure, the user can do nothing else. Use it sparingly!

Slide 55

Slide 55

@hdv@front-end.social modal vs non-modal light dismiss vs explicit dismiss

Slide 56

Slide 56

New message: explicit dismiss @hdv@front-end.social

Slide 57

Slide 57

Font chooser: light dismiss @hdv@front-end.social

Slide 58

Slide 58

@hdv@front-end.social modal vs non-modal light dismiss vs explicit dismiss z-index vs top layer

Slide 59

Slide 59

@hdv@front-end.social fi fi With z-index, you can stack elements on top of each other. The element that is rst in the DOM is painted rst, each subsequent element on top of the previous and

Slide 60

Slide 60

@hdv@front-end.social fi fi With z-index, you can stack elements on top of each other. The element that is rst in the DOM is painted rst, each subsequent element on top of the previous and z-index: 1;

Slide 61

Slide 61

@hdv@front-end.social With z-index, you can stack elements on top of each other. The element that is rst in the DOM is painted rst, The element that each subsequent rstofin the DOM element onistop is painted rst, the previous and each subsequent element on top of the previous and fi fi fi fi z-index: 2;

Slide 62

Slide 62

@hdv@front-end.social The top layer is above everything else, its own layer above the main document fi fi fi fi drafts.csswg.org/css-position-4/#top-layer The element that is rst in the DOM is painted rst, The element that each subsequent rstofin the DOM element onistop is painted rst, the previous and each subsequent element on top of the previous and

Slide 63

Slide 63

@hdv@front-end.social The top layer is above everything else, its own layer above the main document drafts.csswg.org/css-position-4/#top-layer

Slide 64

Slide 64

@hdv@front-end.social Layered based on order of top layer addition, not z-index fi fi fi fi drafts.csswg.org/css-position-4/#top-layer The element that is rst in the DOM is painted rst, The element that each subsequent rstofin the DOM element onistop is painted rst, the previous and each subsequent element on top of the previous and top layer, top layer, top layer, top layer, top layer, top layer

Slide 65

Slide 65

@hdv@front-end.social Layered based on order of top layer addition, not z-index fi fi fi fi drafts.csswg.org/css-position-4/#top-layer The element that is rst in the DOM is painted rst, The element that each subsequent top layer, top rstofin the DOM element onistop layer, top layer, is painted rst, the previous and top layer, top each subsequent layer, top layer also top layer, element on top of also top layer, the previous and also top layer, also top layer,

Slide 66

Slide 66

@hdv@front-end.social modal vs non-modal light dismiss vs explicit dismiss z-index vs top layer backdrop

Slide 67

Slide 67

@hdv@front-end.social Sometimes elements have a backdrop. Top layer elements have a built-in styleable backdrop (::backdrop) fullscreen.spec.whatwg.org/#::backdrop-pseudo-element

Slide 68

Slide 68

@hdv@front-end.social modal vs non-modal light dismiss vs explicit dismiss z-index vs top layer backdrop keyboard focus trap

Slide 69

Slide 69

@hdv@front-end.social keyboard focus trap Sometimes you want to prevent users from exiting a component with their Tab key. This is always temporary.

Slide 70

Slide 70

@hdv@front-end.social modal vs non-modal light dismiss vs explicit dismiss z-index vs top layer backdrop keyboard focus trap

Slide 71

Slide 71

@hdv@front-end.social modal vs non-modal <dialog>

<dialog> with showModal() dismiss light dismiss vs explicit with show() popover vs top layer z-index backdrop keyboard focus trap

Slide 72

Slide 72

@hdv@front-end.social modal vs non-modal light dismiss vs explicit dismiss popover=”auto” popover=”manual” z-index vs top layer backdrop any <dialog> keyboard focus trap

Slide 73

Slide 73

@hdv@front-end.social modal vs <dialog> popover non-modal <dialog> with showModal() vs explicit dismiss light dismiss with show() z-index vs top layer backdrop keyboard focus trap

Slide 74

Slide 74

@hdv@front-end.social modal vs non-modal w/ ::backdrop browser provided light dismiss vs consider if modal <dialog> not better popover explicit dismiss <dialog> with showModal() z-index vs top layer backdrop keyboard focus trap

Slide 75

Slide 75

@hdv@front-end.social modal vs non-modal w/ ::backdrop browser provided light dismiss vs consider if modal <dialog> not better popover explicit dismiss <dialog> with showModal() z-index vs top layer backdrop keyboard focus trap

Slide 76

Slide 76

@hdv@front-end.social modal vs non-modal light dismiss vs explicit dismiss browser provided vs top layer z-index <dialog> with showModal() backdrop keyboard focus trap

Slide 77

Slide 77

@hdv@front-end.social semantics

Slide 78

Slide 78

“What is this thing?” — philosophers @hdv@front-end.social

Slide 79

Slide 79

“What is this thing in this page/app?” —web developers @hdv@front-end.social

Slide 80

Slide 80

@hdv@front-end.social

<h1> heading

Slide 81

Slide 81

@hdv@front-end.social

<h1> heading

Slide 82

Slide 82

@hdv@front-end.social <a> link

Slide 83

Slide 83

@hdv@front-end.social

<li> list item

Slide 84

Slide 84

@hdv@front-end.social

<dialog> dialog

Slide 85

Slide 85

@hdv@front-end.social

<div> [no role]

Slide 86

Slide 86

@hdv@front-end.social

<div role=”link”> link

Slide 87

Slide 87

@hdv@front-end.social

<div popover> [no role]

Slide 88

Slide 88

@hdv@front-end.social The popover attribute adds behaviour, not semantics (you choose a role based on the situation)

Slide 89

Slide 89

@hdv@front-end.social For components that are like a smaller window / subwindow on top of the main page dialogs <dialog> or role=”dialog”

Slide 90

Slide 90

@hdv@front-end.social For components that are like a smaller window / subwindow on top of the main page

Slide 91

Slide 91

@hdv@front-end.social For components that are like a smaller window / subwindow on top of the main page

Slide 92

Slide 92

@hdv@front-end.social For components that let the user choose from a list, the listbox wraps the choices listbox role=”listbox”

Slide 93

Slide 93

@hdv@front-end.social For components that let the user choose from a list, the listbox wraps the choices listbox role=”listbox” listbox role=”listbox”

Slide 94

Slide 94

@hdv@front-end.social listbox role=”listbox”

Slide 95

Slide 95

@hdv@front-end.social For components that o fer the user a list of choices that are actions (like in an application). menus f role=”menu”

Slide 96

Slide 96

@hdv@front-end.social For components that o fer the user a list of choices that are actions (like in an application). menus f role=”menu”

Slide 97

Slide 97

@hdv@front-end.social For components that o fer the user a list of choices that are actions (like in an application). menus f role=”menu”

Slide 98

Slide 98

@hdv@front-end.social Plain text suggestions tooltips role=”tooltip”

Slide 99

Slide 99

tooltips role=”dialog” More than plain text, maybe better as toggletips

Slide 100

Slide 100

@hdv@front-end.social dialogs <dialog> or role=”dialog” tooltips role=”tooltip” menus role=”menu” role=”dialog”

Slide 101

Slide 101

@hdv@front-end.social semantics <dialog> dialog (implicit) popover it depends. You choose an apt role, could be dialog, listbox, menu or tooltip

Slide 102

Slide 102

@hdv@front-end.social positioning

Slide 103

Slide 103

@hdv@front-end.social

g o l a i d < popover ) ( l a d o M w o h s h t i w Both are centered by default

Slide 104

Slide 104

@hdv@front-end.social log.hidde.blog

Slide 105

Slide 105

@hdv@front-end.social

Slide 106

Slide 106

@hdv@front-end.social // concert-list.njk <ol> {% for concert in concerts %} <li> … </li> {% endfor %} </ol>

Slide 107

Slide 107

@hdv@front-end.social // concert-list.njk <ol> {% for concert in concerts %} <li> … {% if concert.images %} <button type=”button” data-dialogtarget=”{{ dialogID }}” aria-label=”Details for {{ concert.info }}” <svg aria-hidden=”true” focusable=”false”>…></svg> </button> {% endif %} </li> {% endfor %} </ol>

Slide 108

Slide 108

@hdv@front-end.social // concert-list.njk <ol> {% for concert in concerts %} <li> … {% if concert.images %} <button type=”button” data-dialogtarget=”{{ dialogID }}” aria-label=”Details for {{ concert.info }}” <svg aria-hidden=”true” focusable=”false”>…></svg> </button> <dialog id=”{{ dialogID }}” aria-label=”Details for {{ concert.info }}”> <button type=”button” data-dialogclose=”{{ dialogID }}” aria-label=”Close”> <svg aria-hidden=”true” focusable=”false”>… </button> … </dialog> {% endif %} </li> {% endfor %}

Slide 109

Slide 109

@hdv@front-end.social // concert-list.js const dialogOpeners = document.querySelectorAll(‘[data-dialogtarget]’);

Slide 110

Slide 110

@hdv@front-end.social // concert-list.js const dialogOpeners = document.querySelectorAll(‘[data-dialogtarget]’); for (let i = 0; i < dialogOpeners.length; i++) { const opener = dialogOpeners[i]; const correspondingDialog = document.querySelector( #${opener.getAttribute('data-dialogtarget')}); }

Slide 111

Slide 111

@hdv@front-end.social // concert-list.js const dialogOpeners = document.querySelectorAll(‘[data-dialogtarget]’); for (let i = 0; i < dialogOpeners.length; i++) { const opener = dialogOpeners[i]; const correspondingDialog = document.querySelector( #${opener.getAttribute('data-dialogtarget')}); opener.addEventListener(‘click’, function() { correspondingDialog.showModal(); }); }

Slide 112

Slide 112

@hdv@front-end.social

Slide 113

Slide 113

@hdv@front-end.social

Slide 114

Slide 114

@hdv@front-end.social

Slide 115

Slide 115

@hdv@front-end.social

Slide 116

Slide 116

@hdv@front-end.social

Slide 117

Slide 117

@hdv@front-end.social

g o l a i d < ✅ popover ) ( l a d o M w o h s h t i w Both are centered by default

Slide 118

Slide 118

@hdv@front-end.social // book-list.njk … <button type=”button” popovertarget=”filters”> Toggle filters </button> …

Slide 119

Slide 119

@hdv@front-end.social // book-list.njk … <button type=”button” popovertarget=”filters”> Toggle filters </button> <div popover role=”menu” id=”filters”> <button type=”button”> Show only Dutch authors </button> <button type=”button”> Show only books with blue covers </button> <button type=”button”> Show only books rated > 3 stars </button> </div> …

Slide 120

Slide 120

@hdv@front-end.social // book-list.njk … <button type=”button” popovertarget=”filters”> Show filters </button> <div popover role=”menu” id=”filters”> <button type=”button” role=”menuitem” autofocus> Show only Dutch authors </button> <button type=”button” role=”menuitem” tabindex=”-1”> Show only books with blue covers </button> <button type=”button” role=”menuitem” tabindex=”-1”> Show only books rated > 3 stars </button> </div> …

Slide 121

Slide 121

@hdv@front-end.social // book-list.njk … <button type=”button” popovertarget=”filters”> Show filters </button> <div popover role=”menu” id=”filters”> <button type=”button” role=”menuitem” autofocus> Show only Dutch authors </button> <button type=”button” role=”menuitem” tabindex=”-1”> Show only books with blue covers </button> <button type=”button” role=”menuitem” tabindex=”-1”> Show only books rated > 3 stars </button> </div> …

Slide 122

Slide 122

@hdv@front-end.social // book-list.njk … <button type=”button” popovertarget=”filters”> Show filters </button> <div popover role=”menu” id=”filters”> <button type=”button” role=”menuitem” autofocus> Show only Dutch authors </button> <button type=”button” role=”menuitem” tabindex=”-1”> Show only books with blue covers </button> <button type=”button” role=”menuitem” tabindex=”-1”> Show only books rated > 3 stars </button> </div> …

Slide 123

Slide 123

@hdv@front-end.social // book-list.njk … <button type=”button” popovertarget=”filters”> Show filters </button> <div popover role=”menu” id=”filters”> <button type=”button”> Show only Dutch authors </button> <button type=”button”> Show only books with blue covers </button> <button type=”button”> Show only books rated > 3 stars </button> </div> …

Slide 124

Slide 124

@hdv@front-end.social

g o l a i d < ✅ popover ) ( l a d o M w o h s h t i w ✅ Both are centered by default

Slide 125

Slide 125

@hdv@front-end.social popover positioning Option 1: calculate (yourself or with a library) // book-list.njk … <script type=”module”> import { computePosition } from ‘https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.2.9/+esm’; </script> …

Slide 126

Slide 126

@hdv@front-end.social popover positioning Option 1: calculate (yourself or with a library) // book-list.njk … <script type=”module”> import { computePosition } from ‘https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.2.9/+esm’; const popover = document.querySelector(‘[popover]’); </script> …

Slide 127

Slide 127

@hdv@front-end.social popover positioning Option 1: calculate (yourself or with a library) // book-list.njk … <script type=”module”> import { computePosition } from ‘https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.2.9/+esm’; const popover = document.querySelector(‘[popover]’); popover.addEventListener(‘toggle’, function(e) { }); </script> …

Slide 128

Slide 128

@hdv@front-end.social popover positioning Option 1: calculate (yourself or with a library) // book-list.njk … <script type=”module”> import { computePosition } from ‘https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.2.9/+esm’; const popover = document.querySelector(‘[popover]’); popover.addEventListener(‘toggle’, function(e) { const invoker = document.querySelector([popovertarget="${popover.getAttribute('id')}"); }); </script> …

Slide 129

Slide 129

@hdv@front-end.social popover positioning Option 1: calculate (yourself or with a library) // book-list.njk … <script type=”module”> import { computePosition } from ‘https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.2.9/+esm’; const popover = document.querySelector(‘[popover]’); popover.addEventListener(‘toggle’, function(e) { const invoker = document.querySelector([popovertarget="${popover.getAttribute('id')}"); if (e.newState === ‘open’) { } }); </script> …

Slide 130

Slide 130

@hdv@front-end.social popover positioning Option 1: calculate (yourself or with a library) // book-list.njk … <script type=”module”> import { computePosition } from ‘https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.2.9/+esm’; const popover = document.querySelector(‘[popover]’); popover.addEventListener(‘toggle’, function(e) { const invoker = document.querySelector([popovertarget="${popover.getAttribute('id')}"); if (e.newState === ‘open’) { computePosition(invoker, popover).then(({x, y}) => { Object.assign(popover.style, { left: ${x}px, top: ${y}px, }); }); } }); </script>

Slide 131

Slide 131

@hdv@front-end.social popover positioning Option 1: calculate (yourself or with a library)

Slide 132

Slide 132

@hdv@front-end.social popover positioning Option 1: calculate (yourself or with a library) I did have to override the UA default for the popover’s margin and position

Slide 133

Slide 133

@hdv@front-end.social popover positioning Option 2: anchor positioning drafts.csswg.org/css-anchor-position-1 kizu.dev/anchor-positioning-experiments/

Slide 134

Slide 134

@hdv@front-end.social wrapping up

Slide 135

Slide 135

g o l a i <d @hdv@front-end.social UI considerations HTML element with wide browser support and (as of recently) good accessibility support Semantics popover Positioning New attribute / API in HTML, v1 supported in Chrome stable, coming to other browsers too

Slide 136

Slide 136

thank you! Questions @hdv on most platforms Slides/resources talks.hiddedevries.nl Thanks Scott O’Hara, Adrian Roselli, Jhey Tompkins