Skip to content

Bits of .NET

Daily micro-tips for C#, SQL, performance, and scalable backend engineering.

  • Asp.Net Core
  • C#
  • SQL
  • JavaScript
  • CSS
  • About
  • ErcanOPAK.com
  • No Access
  • Privacy Policy
HTML

Use HTML Dialog Element for Accessible Modals (Forget JavaScript Libraries)

- 01.02.26 | 01.02.26 - ErcanOPAK

Still using Bootstrap modals or custom JavaScript? The native <dialog> element handles accessibility, focus trapping, and backdrop clicks automatically.

The Complete HTML:

<!-- The dialog element -->
<dialog id="myModal">
  <form method="dialog">
    <h2>Confirm Action</h2>
    <p>Are you sure you want to delete this item?</p>
    
    <div class="actions">
      <button value="cancel">Cancel</button>
      <button value="confirm" autofocus>Delete</button>
    </div>
  </form>
</dialog>

<!-- Trigger button -->
<button onclick="document.getElementById('myModal').showModal()">
  Delete Item
</button>

The JavaScript (Minimal):

const dialog = document.getElementById('myModal');

// Listen for dialog close
dialog.addEventListener('close', () => {
  const returnValue = dialog.returnValue;
  
  if (returnValue === 'confirm') {
    console.log('User confirmed!');
    // Perform delete action
  } else {
    console.log('User cancelled');
  }
});

// Close on backdrop click
dialog.addEventListener('click', (e) => {
  if (e.target === dialog) {
    dialog.close('cancel');
  }
});

What You Get for Free:
✅ Focus trapping (Tab cycles only within dialog)
✅ Esc key closes the dialog
✅ Backdrop element (no separate div needed)
✅ ARIA attributes (role=”dialog”, aria-modal=”true”)
✅ Screen reader announcement
✅ Return value from form submission
✅ Stack management (multiple dialogs work correctly)

The CSS:

dialog {
  padding: 2rem;
  border: none;
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
  max-width: 500px;
  width: 90%;
}

/* The backdrop */
dialog::backdrop {
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(3px);
}

/* Animation */
dialog[open] {
  animation: slide-up 0.3s ease-out;
}

@keyframes slide-up {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Close animation */
dialog.closing {
  animation: slide-down 0.3s ease-in forwards;
}

@keyframes slide-down {
  to {
    opacity: 0;
    transform: translateY(30px);
  }
}

Advanced – Prevent Accidental Close:

const dialog = document.getElementById('myModal');

dialog.addEventListener('cancel', (e) => {
  // User pressed Esc
  if (hasUnsavedChanges()) {
    e.preventDefault(); // Block the close
    
    if (confirm('You have unsaved changes. Close anyway?')) {
      dialog.close('cancel');
    }
  }
});

Comparison with Bootstrap Modal:

// Bootstrap Modal
<!-- 50 lines of HTML -->
<!-- Requires Bootstrap JS: 60KB -->
<!-- Requires jQuery: 85KB -->
<!-- Total: 145KB + HTML complexity -->

// Native <dialog>
<!-- 10 lines of HTML -->
<!-- 15 lines of JavaScript -->
<!-- No libraries -->
<!-- Total: 0KB dependencies -->

Browser Support:
✅ Chrome 37+
✅ Edge 79+
✅ Firefox 98+
✅ Safari 15.4+

That’s 98%+ of all browsers. For older browsers, there’s a 2KB polyfill.

Pro Tip – Nested Dialogs:

<dialog id="outer">
  <h2>Main Dialog</h2>
  <button onclick="document.getElementById('inner').showModal()">
    Open Nested
  </button>
  
  <dialog id="inner">
    <h3>Nested Dialog</h3>
    <button onclick="this.closest('dialog').close()">Close</button>
  </dialog>
</dialog>

The browser automatically manages the dialog stack. Closing the inner dialog returns focus to the outer one. No manual tracking needed.

Related posts:

HTML Forms Submit Unexpected Data

Get and Use TextBox Values with Javascript

HTML5 Input Validation — Why Your Patterns “Don’t Work”

Post Views: 4

Post navigation

Create Smooth Page Transitions with CSS View Transitions API (No JavaScript)
Debounce vs Throttle in JavaScript: When to Use Which (With Real Examples)

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

March 2026
M T W T F S S
 1
2345678
9101112131415
16171819202122
23242526272829
3031  
« Feb    

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (938)
  • How to add default value for Entity Framework migrations for DateTime and Bool (836)
  • Get the First and Last Word from a String or Sentence in SQL (828)
  • How to select distinct rows in a datatable in C# (801)
  • How to make theater mode the default for Youtube (736)
  • Add Constraint to SQL Table to ensure email contains @ (575)
  • How to enable, disable and check if Service Broker is enabled on a database in SQL Server (554)
  • Average of all values in a column that are not zero in SQL (523)
  • How to use Map Mode for Vertical Scroll Mode in Visual Studio (477)
  • Find numbers with more than two decimal places in SQL (441)

Recent Posts

  • C#: Saving Memory with yield return (Lazy Streams)
  • C#: Why Records are Better Than Classes for Data DTOs
  • C#: Creating Strings Without Memory Pressure with String.Create
  • SQL: Protecting Sensitive Data with Dynamic Data Masking
  • SQL: Writing Readable Queries with Common Table Expressions (CTE)
  • .NET Core: Handling Errors Gracefully with Middleware
  • .NET Core: Mastering Service Lifetimes (A Visual Guide)
  • Git: Surgical Stashing – Don’t Save Everything!
  • Git: Writing Commits That Your Future Self Won’t Hate
  • Ajax: Improving Perceived Speed with Skeleton Screens

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (938)
  • How to add default value for Entity Framework migrations for DateTime and Bool (836)
  • Get the First and Last Word from a String or Sentence in SQL (828)
  • How to select distinct rows in a datatable in C# (801)
  • How to make theater mode the default for Youtube (736)

Recent Posts

  • C#: Saving Memory with yield return (Lazy Streams)
  • C#: Why Records are Better Than Classes for Data DTOs
  • C#: Creating Strings Without Memory Pressure with String.Create
  • SQL: Protecting Sensitive Data with Dynamic Data Masking
  • SQL: Writing Readable Queries with Common Table Expressions (CTE)

Social

  • ErcanOPAK.com
  • GoodReads
  • LetterBoxD
  • Linkedin
  • The Blog
  • Twitter
© 2026 Bits of .NET | Built with Xblog Plus free WordPress theme by wpthemespace.com