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

A presentation at HalfStack Vienna in September 2023 in Vienna, Austria 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 15 September 2023 HalfStack, Vienna

Slide 2

Slide 2

Slide 3

Slide 3

Slide 4

Slide 4

@hdv@front-end.social

Slide 5

Slide 5

@hdv@front-end.social

Slide 6

Slide 6

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

Slide 7

Slide 7

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

Slide 8

Slide 8

@hdv@front-end.social

Slide 9

Slide 9

@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 10

Slide 10

Slide 11

Slide 11

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 12

Slide 12

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 13

Slide 13

@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 14

Slide 14

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

Slide 15

Slide 15

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

Slide 16

Slide 16

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

Slide 17

Slide 17

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

Slide 18

Slide 18

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

Slide 19

Slide 19

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

Slide 20

Slide 20

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

Slide 21

Slide 21

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

Slide 22

Slide 22

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

Slide 23

Slide 23

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

Slide 24

Slide 24

@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 25

Slide 25

@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 26

Slide 26

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

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

Slide 27

Slide 27

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

Slide 28

Slide 28

@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 29

Slide 29

@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 30

Slide 30

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

Slide 31

Slide 31

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

Slide 32

Slide 32

@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 33

Slide 33

@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 34

Slide 34

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

Slide 35

Slide 35

@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 36

Slide 36

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

Slide 37

Slide 37

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

Slide 38

Slide 38

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

Slide 39

Slide 39

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

Slide 40

Slide 40

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

Slide 41

Slide 41

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

Slide 42

Slide 42

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

Slide 43

Slide 43

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

Slide 44

Slide 44

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

Slide 45

Slide 45

@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 46

Slide 46

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

Slide 47

Slide 47

@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 48

Slide 48

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

Slide 49

Slide 49

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

Slide 50

Slide 50

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

Slide 51

Slide 51

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

Slide 52

Slide 52

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

Slide 53

Slide 53

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

Slide 54

Slide 54

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

Slide 55

Slide 55

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

Slide 56

Slide 56

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

Slide 57

Slide 57

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

Slide 58

Slide 58

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

Slide 59

Slide 59

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

Slide 60

Slide 60

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

Slide 61

Slide 61

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

Slide 62

Slide 62

@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 63

Slide 63

@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 64

Slide 64

@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 65

Slide 65

@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 66

Slide 66

@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 67

Slide 67

@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 68

Slide 68

@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 69

Slide 69

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

Slide 70

Slide 70

@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 71

Slide 71

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

Slide 72

Slide 72

@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 73

Slide 73

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

Slide 74

Slide 74

@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 75

Slide 75

@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 76

Slide 76

@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 77

Slide 77

@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 78

Slide 78

@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 79

Slide 79

@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 80

Slide 80

@hdv@front-end.social semantics

Slide 81

Slide 81

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

Slide 82

Slide 82

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

Slide 83

Slide 83

@hdv@front-end.social

<h1> heading

Slide 84

Slide 84

@hdv@front-end.social

<h1> heading

Slide 85

Slide 85

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

Slide 86

Slide 86

@hdv@front-end.social

<li> list item

Slide 87

Slide 87

@hdv@front-end.social

<dialog> dialog

Slide 88

Slide 88

@hdv@front-end.social

<div> [no role]

Slide 89

Slide 89

@hdv@front-end.social

<div role=”link”> link

Slide 90

Slide 90

@hdv@front-end.social

<div popover> [no role]

Slide 91

Slide 91

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

Slide 92

Slide 92

@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 93

Slide 93

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

Slide 94

Slide 94

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

Slide 95

Slide 95

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

Slide 96

Slide 96

@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 97

Slide 97

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

Slide 98

Slide 98

@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 99

Slide 99

@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 100

Slide 100

@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 101

Slide 101

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

Slide 102

Slide 102

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

Slide 103

Slide 103

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

Slide 104

Slide 104

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

Slide 105

Slide 105

@hdv@front-end.social positioning

Slide 106

Slide 106

@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 107

Slide 107

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

Slide 108

Slide 108

@hdv@front-end.social

Slide 109

Slide 109

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

Slide 110

Slide 110

@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 111

Slide 111

@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 112

Slide 112

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

Slide 113

Slide 113

@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 114

Slide 114

@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 115

Slide 115

@hdv@front-end.social

Slide 116

Slide 116

@hdv@front-end.social

Slide 117

Slide 117

@hdv@front-end.social

Slide 118

Slide 118

@hdv@front-end.social

Slide 119

Slide 119

@hdv@front-end.social

Slide 120

Slide 120

@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 121

Slide 121

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

Slide 122

Slide 122

@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 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” 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 124

Slide 124

@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 125

Slide 125

@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 126

Slide 126

@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 127

Slide 127

@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 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’; </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]’); </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) { }); </script> …

Slide 131

Slide 131

@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 132

Slide 132

@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 133

Slide 133

@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 134

Slide 134

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

Slide 135

Slide 135

@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 136

Slide 136

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

Slide 137

Slide 137

@hdv@front-end.social wrapping up

Slide 138

Slide 138

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 139

Slide 139

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