Repository: digdir/designsystemet
Pull request #3281: fix(Dialog): used closedby prop to align with upcoming browser standards
Author: login: eirikbacker
Created: 2025-03-03T09:18:36Z | Merged: 2025-03-03T12:10:07Z

**Dialog**: Used `closedby="none | closerequest | any"` to close backdrop and to align with [upcoming browser standard](https://chromestatus.com/feature/5097714453577728)


Diff:
diff --git a/.changeset/cool-dancers-prove.md b/.changeset/cool-dancers-prove.md
new file mode 100644
index 0000000000..2beae3f178
--- /dev/null
+++ b/.changeset/cool-dancers-prove.md
@@ -0,0 +1,5 @@
+---
+"@digdir/designsystemet-react": patch
+---
+
+**Dialog**: Remove `backdropClose`, and replace with `closedby="none | closerequest | any"` to align with [upcoming browser standard](https://chromestatus.com/feature/5097714453577728)
diff --git a/apps/_components/src/ColorModal/ColorModal.tsx b/apps/_components/src/ColorModal/ColorModal.tsx
index 8ad3b11e90..f6ce36dc80 100644
--- a/apps/_components/src/ColorModal/ColorModal.tsx
+++ b/apps/_components/src/ColorModal/ColorModal.tsx
@@ -45,10 +45,10 @@ export const ColorModal = ({
   return (
     <Dialog
       ref={colorModalRef}
+      closedby='any'
       style={{
         maxWidth: '1100px',
       }}
-      backdropClose
     >
       <Dialog.Block>
         <Heading data-size='xs'>
diff --git a/apps/theme/components/TokenModal/TokenModal.tsx b/apps/theme/components/TokenModal/TokenModal.tsx
index d4bff5e8a9..42f3974c8e 100644
--- a/apps/theme/components/TokenModal/TokenModal.tsx
+++ b/apps/theme/components/TokenModal/TokenModal.tsx
@@ -65,7 +65,7 @@ export const TokenModal = () => {
         className={classes.modal}
         style={{ maxWidth: 1000 }}
         ref={modalRef}
-        backdropClose={true}
+        closedby='any'
       >
         <Dialog.Block>
           <Heading className={classes.modalHeader} data-size='2xs'>
diff --git a/packages/react/src/components/Dialog/Dialog.mdx b/packages/react/src/components/Dialog/Dialog.mdx
index 6dbe0df46c..609e0e9fa9 100644
--- a/packages/react/src/components/Dialog/Dialog.mdx
+++ b/packages/react/src/components/Dialog/Dialog.mdx
@@ -59,10 +59,10 @@ const dialogRef = useRef<HTMLDialogElement>(null);
 
 ### Close on backdrop click
 
-Vi bruker `backdropClose={true}` proppen for å lukke dialogen når brukeren klikker utenfor.
-Dette funker kun dersom du har `modal={true}`, fordi en non-modla har ikke et backdrop.
+Vi bruker `closedby="any"` proppen for å lukke dialogen når brukeren klikker utenfor.
+Dette funker kun dersom du har `modal={true}`, fordi en non-modal har ikke et backdrop.
 
-<Canvas of={DialogStories.BackdropClose} />
+<Canvas of={DialogStories.BackdropClosedbyAny} />
 
 ### Med `Block`
 
diff --git a/packages/react/src/components/Dialog/Dialog.stories.tsx b/packages/react/src/components/Dialog/Dialog.stories.tsx
index 997363e6a6..62da2c06b9 100644
--- a/packages/react/src/components/Dialog/Dialog.stories.tsx
+++ b/packages/react/src/components/Dialog/Dialog.stories.tsx
@@ -127,14 +127,16 @@ export const DialogWithOpenProp: StoryFn<typeof Dialog> = (args) => {
   );
 };
 
-export const BackdropClose: StoryFn<typeof Dialog> = () => {
+export const BackdropClosedbyAny: StoryFn<typeof Dialog> = () => {
   const dialogRef = useRef<HTMLDialogElement>(null);
 
   return (
     <Dialog.TriggerContext>
       <Dialog.Trigger>Open Dialog</Dialog.Trigger>
-      <Dialog ref={dialogRef} backdropClose>
-        <Heading>Dialog med backdropClose og en veldig lang tittel</Heading>
+      <Dialog ref={dialogRef} closedby='any'>
+        <Heading>
+          Dialog med <code>closedby="any"</code> og en veldig lang tittel
+        </Heading>
         <Paragraph>
           Lorem ipsum dolor sit, amet consectetur adipisicing elit. Blanditiis
           doloremque obcaecati assumenda odio ducimus sunt et.
@@ -189,7 +191,7 @@ export const DialogWithForm: StoryFn<typeof Dialog> = () => {
   return (
     <Dialog.TriggerContext>
       <Dialog.Trigger>Open Dialog</Dialog.Trigger>
-      <Dialog ref={dialogRef} onClose={() => setInput('')} backdropClose>
+      <Dialog ref={dialogRef} onClose={() => setInput('')} closedby='any'>
         <Heading style={{ marginBottom: 'var(--ds-size-2)' }}>
           Dialog med skjema
         </Heading>
diff --git a/packages/react/src/components/Dialog/Dialog.tsx b/packages/react/src/components/Dialog/Dialog.tsx
index a01cb11a6e..ccc3d0b9eb 100644
--- a/packages/react/src/components/Dialog/Dialog.tsx
+++ b/packages/react/src/components/Dialog/Dialog.tsx
@@ -18,12 +18,11 @@ export type DialogProps = MergeRight<
      */
     closeButton?: string | false;
     /**
-     * Close on backdrop click.
-     * Only works when `modal` is true.
+     * Light dismiss behavior, allowing to close on backdrop click  by setting `closedby="any"`.
      *
-     * @default false
+     * @default 'closerequest'
      */
-    backdropClose?: boolean;
+    closedby?: 'none' | 'closerequest' | 'any';
     /**
      * Toogle modal and non-modal dialog.
      *
@@ -78,10 +77,10 @@ export const Dialog = forwardRef<HTMLDialogElement, DialogProps>(
       children,
       className,
       closeButton = 'Lukk dialogvindu',
+      closedby = 'closerequest',
       modal = true,
-      open,
       onClose,
-      backdropClose = false,
+      open,
       ...rest
     },
     ref,
@@ -96,13 +95,16 @@ export const Dialog = forwardRef<HTMLDialogElement, DialogProps>(
 
     useEffect(() => {
       const dialog = dialogRef.current;
-      const handleBackdropClick = ({
-        clientY: y,
-        clientX: x,
-        target,
-      }: MouseEvent) => {
+      const handleClosedby = (event: Event) => {
+        const { clientY: y, clientX: x, target } = event as MouseEvent;
+        if (event instanceof KeyboardEvent)
+          return (
+            closedby === 'none' &&
+            event.key === 'Escape' &&
+            event.preventDefault()
+          ); // Skip ESC-key if closedby="none"
         if (window.getSelection()?.toString()) return; // Fix bug where if you select text spanning two divs it thinks you clicked outside
-        if (dialog && target === dialog && backdropClose) {
+        if (dialog && target === dialog && closedby === 'any') {
           const { top, left, right, bottom } = dialog.getBoundingClientRect();
           const isInDialog = top <= y && y <= bottom && left <= x && x <= right;
 
@@ -116,12 +118,14 @@ export const Dialog = forwardRef<HTMLDialogElement, DialogProps>(
       };
 
       dialog?.addEventListener('animationend', handleAutoFocus);
-      dialog?.addEventListener('click', handleBackdropClick);
+      dialog?.addEventListener('click', handleClosedby);
+      dialog?.addEventListener('keydown', handleClosedby);
       return () => {
         dialog?.removeEventListener('animationend', handleAutoFocus);
-        dialog?.removeEventListener('click', handleBackdropClick);
+        dialog?.removeEventListener('click', handleClosedby);
+        dialog?.removeEventListener('keydown', handleClosedby);
       };
-    }, [backdropClose]);
+    }, [closedby]);
 
     /* handle closing */
     useEffect(() => {


Reviews:
user: login: unekinn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T09:47:40Z
user: login: eirikbacker
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T09:50:45Z
user: login: Barsnes
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T10:38:19Z
user: login: unekinn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T11:37:09Z
user: login: mimarz
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-03T12:00:24Z

Review comments:
diff_hunk: @@ -133,7 +133,7 @@ export const BackdropClose: StoryFn<typeof Dialog> = () => {
   return (
     <Dialog.TriggerContext>
       <Dialog.Trigger>Open Dialog</Dialog.Trigger>
-      <Dialog ref={dialogRef} backdropClose>
+      <Dialog ref={dialogRef} closedby="none">
path: packages/react/src/components/Dialog/Dialog.stories.tsx
user: login: unekinn
body: Litt rar endring, sidan innhaldet i dialogen seier "Dialog med backdropClose"?
created_at: 2025-03-03T09:47:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -133,7 +133,7 @@ export const BackdropClose: StoryFn<typeof Dialog> = () => {
   return (
     <Dialog.TriggerContext>
       <Dialog.Trigger>Open Dialog</Dialog.Trigger>
-      <Dialog ref={dialogRef} backdropClose>
+      <Dialog ref={dialogRef} closedby="none">
path: packages/react/src/components/Dialog/Dialog.stories.tsx
user: login: eirikbacker
body: Right, story-innholdet burde oppdateres! Good point <3
created_at: 2025-03-03T09:50:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,5 @@
+---
+"@digdir/designsystemet-react": patch
+---
+
+**Dialog**: Use `closedby="none | closerequest | any"` instead of `backdropClose` to align with [upcoming browser standard](https://chromestatus.com/feature/5097714453577728)
path: .changeset/cool-dancers-prove.md
user: login: Barsnes
body: ```suggestion
**Dialog**: Remove `backdropClose`, and replace with `closedby="none | closerequest | any"` to align with [upcoming browser standard](https://chromestatus.com/feature/5097714453577728)
```
Just to get `backdropClose` earlier in the change so people are more likely to notice
created_at: 2025-03-03T10:36:56Z
author_association: MEMBER
diff_hunk: @@ -59,8 +59,8 @@ const dialogRef = useRef<HTMLDialogElement>(null);
 
 ### Close on backdrop click
 
-Vi bruker `backdropClose={true}` proppen for å lukke dialogen når brukeren klikker utenfor.
-Dette funker kun dersom du har `modal={true}`, fordi en non-modla har ikke et backdrop.
+Vi bruker `closedby="any"` proppen for å lukke dialogen når brukeren klikker utenfor.
+Dette funker kun dersom du har `modal={true}`, fordi en non-modal har ikke et backdrop.
path: packages/react/src/components/Dialog/Dialog.mdx
user: login: Barsnes
body: :D 
created_at: 2025-03-03T10:37:48Z
author_association: MEMBER
diff_hunk: @@ -96,13 +95,16 @@ export const Dialog = forwardRef<HTMLDialogElement, DialogProps>(
 
     useEffect(() => {
       const dialog = dialogRef.current;
-      const handleBackdropClick = ({
-        clientY: y,
-        clientX: x,
-        target,
-      }: MouseEvent) => {
+      const handleClosedby = (event: Event) => {
+        const { clientY: y, clientX: x, target } = event as MouseEvent;
+        if (event instanceof KeyboardEvent)
+          return (
+            closedby === 'none' &&
+            event.key === 'Escape' &&
+            event.preventDefault()
+          ); // Skip ESC-key if closedby="none"
path: packages/react/src/components/Dialog/Dialog.tsx
user: login: unekinn
body: Burde `closedby="none"` ha ein test i `Dialog.test.tsx`? Etter `should call onClose when the Dialog is closed with ESC`
created_at: 2025-03-03T11:37:09Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-03-03T09:18:40Z
body: ###  🦋  Changeset detected

Latest commit: a3b30e76654f1f0544b940833db549d4711e451a

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 4 packages</summary>

  | Name                         | Type  |
| ---------------------------- | ----- |
| @digdir/designsystemet-react | Patch |
| @digdir/designsystemet       | Patch |
| @digdir/designsystemet-css   | Patch |
| @digdir/designsystemet-theme | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/digdir/designsystemet/new/fix/dialog-closedby-prop?filename=.changeset/spotty-candles-carry.md&value=---%0A%22%40digdir%2Fdesignsystemet-react%22%3A%20patch%0A%22%40repo%2Fcomponents%22%3A%20patch%0A%22theme%22%3A%20patch%0A---%0A%0Afix(Dialog)%3A%20used%20closedby%20prop%20to%20align%20with%20upcoming%20browser%20standards%0A)


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-03T09:18:55Z
body: **Preview deployments for this pull request:**

[Storybook](https://pr-3281.storybook.designsystemet.no) - `3. Mar 2025 - 13:04`



[Theme](https://pr-3281.theme.designsystemet.no) - `3. Mar 2025 - 13:05`
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-03T09:20:03Z
body: <h2>Coverage Report</h2>
<table> <thead> <tr> <th align="center">Status</th> <th align="left">Category</th> <th align="right">Percentage</th> <th align="right">Covered / Total</th> </tr> </thead> <tbody> <tr> <td align="center">🔵</td> <td align="left">Lines</td> <td align="right">47.95%</td> <td align="right">3210 / 6694</td> </tr> <tr> <td align="center">🔵</td> <td align="left">Statements</td> <td align="right">47.95%</td> <td align="right">3210 / 6694</td> </tr> <tr> <td align="center">🔵</td> <td align="left">Functions</td> <td align="right">84.55%</td> <td align="right">219 / 259</td> </tr> <tr> <td align="center">🔵</td> <td align="left">Branches</td> <td align="right">77.41%</td> <td align="right">562 / 726</td> </tr> </tbody> </table><details><summary>File Coverage</summary><table> <thead> <tr> <th align="left">File</th> <th align="right">Stmts</th> <th align="right">Branches</th> <th align="right">Functions</th> <th align="right">Lines</th> <th align="left">Uncovered Lines</th> </tr> </thead> <tbody> <tr> <td align="left" colspan="6"><b>Changed Files</b></td> </tr> <tr> <td align="left"><a href="https://github.com/digdir/designsystemet/blob/a3b30e76654f1f0544b940833db549d4711e451a/packages/react/src/components/Dialog/Dialog.tsx">packages/react/src/components/Dialog/Dialog.tsx</a></td> <td align="right">87.95%</td> <td align="right">68.42%</td> <td align="right">75%</td> <td align="right">87.95%</td> <td align="left"><a href="https://github.com/digdir/designsystemet/blob/a3b30e76654f1f0544b940833db549d4711e451a/packages/react/src/components/Dialog/Dialog.tsx#L102-L104" class="text-red">102-104</a>, <a href="https://github.com/digdir/designsystemet/blob/a3b30e76654f1f0544b940833db549d4711e451a/packages/react/src/components/Dialog/Dialog.tsx#L108-L112" class="text-red">108-112</a>, <a href="https://github.com/digdir/designsystemet/blob/a3b30e76654f1f0544b940833db549d4711e451a/packages/react/src/components/Dialog/Dialog.tsx#L116-L118" class="text-red">116-118</a></td> </tr> </tbody> </table></details>
<em>Generated in workflow <a href=https://github.com/digdir/designsystemet/actions/runs/13630425224>#2356</a> for commit <a href="https://github.com/digdir/designsystemet/commit/a3b30e76654f1f0544b940833db549d4711e451a">a3b30e7</a> by the <a href="https://github.com/davelosert/vitest-coverage-report-action">Vitest Coverage Report Action</a></em>

<!-- vitest-coverage-report-marker-root -->
author_association: CONTRIBUTOR
user: login: mimarz
created_at: 2025-03-03T12:01:24Z
body: hmm, why did this not trigger and error 🙈 
<img width="1564" alt="image" src="https://github.com/user-attachments/assets/83bcfd49-4519-409f-9705-17912693953f" />

author_association: COLLABORATOR