惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Spread Privacy
Spread Privacy
K
Kaspersky official blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Forbes - Security
Forbes - Security
Hacker News - Newest:
Hacker News - Newest: "LLM"
The Last Watchdog
The Last Watchdog
SecWiki News
SecWiki News
Attack and Defense Labs
Attack and Defense Labs
Google DeepMind News
Google DeepMind News
Security Archives - TechRepublic
Security Archives - TechRepublic
S
Secure Thoughts
WordPress大学
WordPress大学
Microsoft Security Blog
Microsoft Security Blog
P
Proofpoint News Feed
云风的 BLOG
云风的 BLOG
V
Visual Studio Blog
Security Latest
Security Latest
TaoSecurity Blog
TaoSecurity Blog
Cyberwarzone
Cyberwarzone
S
SegmentFault 最新的问题
Cloudbric
Cloudbric
aimingoo的专栏
aimingoo的专栏
S
Schneier on Security
N
Netflix TechBlog - Medium
MyScale Blog
MyScale Blog
T
The Blog of Author Tim Ferriss
H
Hacker News: Front Page
C
Cybersecurity and Infrastructure Security Agency CISA
小众软件
小众软件
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
AWS News Blog
AWS News Blog
AI
AI
G
GRAHAM CLULEY
IT之家
IT之家
P
Privacy & Cybersecurity Law Blog
L
Lohrmann on Cybersecurity
Last Week in AI
Last Week in AI
D
Docker
Recent Announcements
Recent Announcements
O
OpenAI News
T
Threat Research - Cisco Blogs
GbyAI
GbyAI
S
Security @ Cisco Blogs
T
Troy Hunt's Blog
C
Check Point Blog
博客园 - 三生石上(FineUI控件)
A
About on SuperTechFans
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
N
News and Events Feed by Topic

Ben Frain

Scroll indicators on tables with background colours using animation-timeline Review: SoundPEATS Clip1 Open ear clip-on headphones VS Code – highlight just the active indent guide Review: MoErgo Go60, a split ergonomic and fully programmable keyboard Review: Kinesis mWave mechanical ergonomic and programmable keyboard iOS26 Safari theme-color/tab-tinting with fixed position elements is a mess New Book: Responsive Web Design with HTML5 and CSS, 5th Edition Use @supports with a proxy feature/value for features you can’t test for (@starting-style) First adventures in View Transitions Review: Benq Screenbar Pro and Halo lightbars. The kit you never knew you needed! Center items in a container, and make then left aligned when they overflow A single element CSS donut timer/countdown timer, that can sit on any background Review: Open Ear Headphones – Bose Open Ultra v Huawei FreeClip In search of the perfect autocomplete for CSS Managing multiple versions of node, without NVM or additional tools Review: Keychron Q14 Max Alice 96 Key mechanical keyboard NEW VIDEO COURSE: Responsive Web Design with HTML5 and CSS Is CSS Grid really slower than Flexbox? Review: Advantage360 Pro Signature Edition 2024 mechanical ergonomic keyboard More Keys or Fewer Keys for mechanical keyboards Yes! You can use position: sticky and overflow together Neovim – how to do project-wide find and replace? Review: Keyboardio Model 100, split, wooden, mechanical keyboard Struggling to learn SwiftUI How to create rounded gradient borders with any background in CSS How to get equal size icons in the cmp completion menu of Neovim with Kitty terminal Review: Dygma Defy, split, mechanical, programmable ergonomic keyboard What’s the best way to reset WAAPI chained animations? Using CSS @property inside shadowRoot (web components) workaround Dynamically create a ref for items when iterating over them in lit.dev templates Selecting and pausing running animations in Lit Web Components New Web APIs — a popover on top of a dialog element can’t be interacted with? Review: ZSA Voyager, split, mechanical keyboard Russel Brand, narcissism, and a sadly common pattern… When it comes to text editors, I feel like Goldilocks Simple settings for writing and converting markdown with Sublime Text Review: The ZSA Platform tenting kit for the Moonlander keyboard Logitech MX Master 3/3s scroll wheel fix Building a line graph with CSS clip-mask Review: Dell 6K 32″ Monitor U3224KBA I broke my keyboard! Swapping the key switches in the Kinesis Advantage360 Pro HUGE macOS Productivity boost: Set-up simple, keyboard only, instant App switching and arrangement Adding to $PATH for a central location for Neovim/NPM tools Neovim Power Tips: Volume 2 Review: MoErgo Glove80, split, wireless, columnar ergonomic keyboard with RGB Review: Kinesis Advantage 360 Pro — split ergo mechanical keyboard Review: Dactyl Manuform – an ergonomic, custom built mechanical keyboard How to animate along an SVG path at the same time the path animates? Getting the context of Web Components (lit)
So, you want a React modal that uses the <dialog> element and transitions in AND out?
Ben Frain · 2026-04-14 · via Ben Frain

I had to make a modal recently, it happened to be in React, so some of what follows is React specific, but the principles are all good ol' CSS/HTML and JavaScript, so don't necassarily go running just because I'm using React here…. wait, where are you going? Come back…

Oh well, just you left I guess. Nevermind, let's make a decent modal with the <dialog> element in React.

The basics of the dialog element

I've covered using <dialog> elements with standard HTML, CSS, and JavaScript in my book, Responsive Web Design with HTML5 and CSS but for React, things are just slightly different. To quote myself from that title (because I'm clearly that conceited), here is the briefest of primers on the <dialog> element.

When the modal dialog is opened, it automatically gets centered and focused, and the backdrop of the dialog — which is automatically inserted — covers everything below, preventing any of the underlying content from being interacted with. You can dismiss the dialog with the button inside it, or a keyboard press of the Esc key; the keyboard support is functionality you get for free!

That's quite a bit of functionality provided 'free' by the web platform. We will build on that.

Requirments for the modal

If you have ever tried to implement a fully working modal without the <dialog> element in the past, you will appreciate how tricky some of the accessibility features are to get right. As such, and with very good browser support nowadays, the dialog is our starting point.

Here is the list of requirements I gave myself for the modal.

  • Can be dismissed with Escape on Keyboard
  • Transitions in
  • Transitions out
  • Backdrop also transitions in/out
  • Actually works on Safari
  • Can be dismissed with a button inside
  • Can be dismissed by clicking the backdrop
  • Can put any content inside we want

What you will end up with (demo)

I'm using Codesandbox editor here to demo the modal.

Here is what you should have at the end. Click to open the modal, and to dismiss, press escape, click the backdrop, or cross. Most importantly, marvel at the fact that the dialog transitions in and out, and actually does all the same things in Safari (no mean feat!).

Some of the requirements were handled 'out of the box' by <dialog>, such as using Escape to dismiss. Everything else took a little tinkering, so I'll highlight each below. I'll cover functional stuff first, and then onto the transitions.

Dismissed by clicking a button inside

The specification shows using a <form> element inside for the content. That also then lets you easily return the value of the button when the form is submitted (you will need to add the method="dialog" attribute to the dialog too). However, I was feeling contrarian so opted to just use an inner <div>. The use of this inner element is actually important, and we will get to why shortly.

But in terms of dismissing the <dialog> I have a button:

<button
    ref={buttonRef}
    className={styles.close}
    onClick={(e) => closeDialog(e)}
>
    <span className={styles.cross}></span>
</button>

There is a ref on there and the click handler passes the event to my closeDialog method. This is important because we need to know if the thing being clicked is actually the button, the background, or the content. If it is the content, we don't want to dismiss the modal, otherwise we do.

Here is the closeDialog, and you can see that only if the thing is the close button (buttonRef), or the <dialog> itself, we close the <dialog>. The content is wrapped in an inner <div>, so doesn't activate the closeDialog.

function closeDialog(e) {
    if (e.target === buttonRef.current || e.target === dialogRef.current) {
        dialogRef.current?.close();
    }
}

Dismiss by clicking the backdrop

In Chrome/Edge etc, if you want to be able to dismiss the <dialog> element by clicking the backdrop, it's as simple as adding the closedby="any" attribute to the dialog element.

Safari however, still doesn't have this – the useless POS – so you'll need to add more code and elements to deal with it.

Thanks Safari.

The crux of being able to workaround this is that the ::backdrop pseudo element, is actually part of the <dialog> element, so by wrapping the content in our aformentioned element, then clicks from your actual content can be distinguished from clicks on the backdrop. You have seen the closeDialog method above that handles that already – because when you are clicking the backdrop, you are effectively clicking the dialog, and not the content, we dismiss it.

Adding styling options

There are a bunch of optional props that can be passed in my implementation to control the aesthetics; border radii, color, speed of transitions etc – you may want more of less of that stuff but many of these get exposed in the CSS as Custom Properties:

const styleProps = {
    "--padding": `${padding}px`,
    "--radii": `${radii}px`,
    "--height": `${height}`,
    "--width": `${width}`,
    "--transitionSpeed": `${transitionSpeed}`,
} as React.CSSProperties;

These Custom Properties are all applied to the structure of the modal, on the <dialog> element.

The content of the modal

In terms of what is shown inside the modal, I use a content prop and just send in whatever component I want to see in there. In my demo, the content sent in is simple as:

const ModalTestInner = () => <div>Here is the content</div>;

Transitioning the <dialog> in and out

For transitioning the appearance of the <dialog> in and out, I'm leaning on modern CSS. In my mind, this part of the requirements is the domain of 'progressive enhancement'.

To transition the modal in, I'm using CSS's @starting-style which allows you to define in CSS what visual state the element should be in BEFORE it is added to the DOM. This means we can add a transition to the element, and when it is added to the DOM it will transition from its starting-style, to the default style.

When you add @starting-style you always want to add it after any other equivalent styles as it does not affect specificity. It stands to reason therefore that if it is not after other styles, those other styles will superceded the prior ones and you won't get the desired effect.

Because I wanted @starting-styles for both the <dialog> and its ::backdrop, rather than nesting them in the normal rules for the element, as I usually do, I actually split them out below the other rules.

@starting-style {
    .dialog[open],
    .dialog:not([open]) {
        transform: scale(0.8);
        opacity: 0;
    }
    .dialog[open]::backdrop {
        opacity: 0;
    }
}

But look, defining the starting styles is acually the easy part. I think the amount of properties and values I have had to add in CSS alongside these to get this actually working correctly (cough Safari cough), and the order in which they need to be added is a little bit like alchemy. It's one of the main reasons I wrote this post, so that I would have a reference for my future self (wait, you think I did this for you? Mwah ha ha!).

Principal in terms of difficulty, is getting the modal to transition when dismissed. If there is a 'secret sauce' to remember, it is adding allow-discrete to your transitions. Here I am transitioning 'all' properties, but if you are transitioning different properties at different speeds/easings, ensure that display and overlay get allow-discrete added. That enables the browser to transition those two properties, which are not ordinarily 'transitionable'.

Transitioning the ::backdrop element

By default, even if you add a transition to the dialog element, the ::backdrop does not transition with it. To get that transitioning too, it needs its own transition, complete with allow-discrete adding, and the various open states adding too. Check those out in the code.

Go home Safari, you're drunk

No write-up seems to be complete without a little special attention to get Safari to behave predictably. In this example, for reasons known only to those in Cupertino, when you dismiss a <dialog>, it's inset values get changed from whatever the defaults are. This led to the dialog jumping down the page when dismissed.

To remedy that, we need to add a distinct inset: 0 and position: fixed declaration in the dialog. An easy fix, but one that took a good few minutes to figure out.

Summary

It felt like more work than it should be, CSS wise, to get the transitions working, including the ::backdrop, as I desired. This approach also lets you have alternative entry and exit positions/transitions so play about to get your desirsed effect.

Hopefully, this gives you the basis for an accessible modal in React that still enjoys the visual flourish you would like and some flexibility to restyle as needed.