What if I told you that you could open a modal, close it, and control a popover menu using HTML alone?

No addEventListener().

No onclick handlers.

No JavaScript functions just to open and close things.

Modern HTML supports Invoker Commands, using two attributes:

  • commandfor
  • command

And they're surprisingly powerful.

In this tutorial, we'll explore them by building interactive features inside a real luxury real estate website called HAVEN Estates.

Instead of demonstrating everything with empty boxes, we'll use actual property cards, detailed property dialogs, a navigation popover, and a private-viewing form.

Prefer watching rather than reading? Watch the full step-by-step YouTube tutorial.

If you want to go deeper and build full responsive layouts faster, check out my guide: Build Any Responsive Layout with CSS Grid & Flexbox. It walks through building real pages component by component.

Let's get started.

1. The old way: JavaScript event handlers

Imagine you're building a website with a button that opens a modal.

Traditionally, you might write:

<button id="open-dialog"> View Property </button> <dialog id="property-dialog"> <h2>Villa Sereno</h2> <p>Explore this luxury residence.</p> </dialog> 

Then use JavaScript:

const openButton = document.querySelector("#open-dialog"); const dialog = document.querySelector("#property-dialog"); openButton.addEventListener("click", () => { dialog.showModal(); }); 

This works, and JavaScript is still essential when you need custom application logic. But for a simple built-in interaction like opening a dialog, we no longer need to write this event handler in supporting browsers.

2. Meet commandfor and command

Here's the HTML-only alternative:

<button type="button" commandfor="property-dialog" command="show-modal" > View Property </button> <dialog id="property-dialog"> <h2>Villa Sereno</h2> <p>Explore this luxury residence.</p> </dialog> 

That's it. The browser knows which dialog to open and what action to perform.

commandfor = WHERE

The commandfor attribute identifies the target element:

commandfor="property-dialog" 

This connects the button to the element with the matching ID:

<dialog id="property-dialog"> 

Think of it as answering: Which element should this button control?

command = WHAT

The command attribute tells the browser what action to perform:

command="show-modal" 

Think of it as answering: What should happen to that element?

The easiest way to remember the difference:

commandfor = WHERE

command = WHAT

That's the foundation of HTML Invoker Commands.

3. The commands you need to know

For built-in dialogs and popovers, common command values include:

Command Purpose
show-modal Open a dialog as a modal
close Close a dialog
request-close Request a dialog to close
show-popover Show a popover
hide-popover Hide a popover
toggle-popover Toggle a popover

Some values target <dialog> elements, while others target elements with the popover attribute. That distinction is important.

Let's use them in our actual project.

4. Build a property dialog in HAVEN Estates

HAVEN Estates is a premium property website featuring luxury residential listings. Each property card contains a quick-view button.

Our goal is simple: when a visitor clicks a property button, a dialog should open with more information.

Let's use one of the properties: Villa Sereno.

Step 1: Create the button

Inside our property card, we have a button similar to this:

<button class="image-action" type="button" commandfor="property-sereno" command="show-modal" aria-label="Quick view of Villa Sereno" > ↗ </button> 

Notice three important things:

  1. It's a real <button>.
  2. The button points to property-sereno.
  3. The command is show-modal.

Now we need to create the target.

Step 2: Add the dialog

<dialog class="modal property-dialog" id="property-sereno" aria-labelledby="dialog-title-sereno" > <div class="modal-body"> <h2 id="dialog-title-sereno"> Villa Sereno </h2> <p> A luxury Mediterranean residence designed for exceptional coastal living. </p> <button type="button" commandfor="property-sereno" command="close" > Close </button> </div> </dialog> 

The id matches the button's commandfor value. That's what makes the connection work.

Click the quick-view button, and the browser opens the dialog—without a custom click handler or a JavaScript function to call showModal().

Step 3: Close the dialog

Look at our close button:

<button type="button" commandfor="property-sereno" command="close" > Close </button> 

We target the same dialog, but change the command from show-modal to close.

What about request-close?

Our HAVEN project also uses this pattern for the close icon:

<button type="button" commandfor="property-sereno" command="request-close" aria-label="Close property details" > × </button> 

The difference matters:

  • close directly closes the dialog.
  • request-close requests closure through the dialog's cancelable close-request behavior.

That can be useful when an application needs to intercept a closing request, such as warning about unsaved changes. For our simple property preview, both normally result in a closed dialog.

5. Style the dialog with CSS

Native HTML controls don't mean we have to accept boring designs.

The HAVEN Estates dialogs use custom styling, property imagery, elegant typography, and a backdrop. Here's a simplified example:

.modal { width: min(90vw, 760px); max-height: 90dvh; padding: 0; border: 0; border-radius: 18px; overflow: auto; background: #fff; color: #24211d; box-shadow: 0 24px 80px rgba(0, 0, 0, 0.25); } .modal::backdrop { background: rgba(10, 14, 18, 0.65); backdrop-filter: blur(6px); } 

The ::backdrop pseudo-element styles the layer behind a modal dialog. And max-height: 90dvh limits the dialog to the dynamic viewport height, with scrolling available for longer content.

Native behavior and custom design can work together.

6. Create a navigation menu with the Popover API

Now let's move to another real feature in HAVEN Estates: the navigation button.

Clicking it reveals an additional panel containing links such as:

  • Discover properties
  • Our philosophy
  • Exceptional amenities
  • Client stories
  • Contact us

The panel also has a Book a private viewing button.

We can build this interaction with HTML commands and the Popover API.

Step 1: Create the trigger

<button class="menu-trigger" type="button" commandfor="site-menu" command="toggle-popover" aria-label="Open navigation" > ☰ </button> 

This time our command is toggle-popover, and the target is site-menu.

Step 2: Create the popover

<nav id="site-menu" class="site-menu" popover aria-label="Additional navigation" > <div class="menu-top"> <strong>Explore HAVEN</strong> <button type="button" commandfor="site-menu" command="hide-popover" aria-label="Close menu" > × </button> </div> <a href="#properties">Discover properties</a> <a href="#story">Our philosophy</a> <a href="#amenities">Exceptional amenities</a> <a href="#testimonials">Client stories</a> <a href="#contact">Contact us</a> </nav> 

The important addition is the popover attribute.

Because we're using the default automatic popover behavior, the browser provides useful built-in interactions:

  • The command button can toggle the popover.
  • Escape can dismiss the open popover.
  • Clicking outside can dismiss it.
  • The open popover is placed in the top layer.

All without custom JavaScript for those behaviors.

However, a popover isn't automatically a modal dialog: it does not make the rest of the page inert or trap keyboard focus.

7. Add a viewing form without an open-modal handler

Another feature in HAVEN Estates is the private-viewing form. When a visitor clicks Schedule a viewing, a modal dialog opens.

The trigger uses the same pattern:

<button type="button" commandfor="viewing-dialog" command="show-modal" > Schedule a viewing </button> 

And the target is:

<dialog id="viewing-dialog"> <h2>Schedule a viewing</h2> <form method="dialog"> <label for="full-name">Full name</label> <input id="full-name" name="name" required> <label for="email">Email address</label> <input id="email" name="email" type="email" required> <button type="submit">Close form preview</button> </form> </dialog> 

Here's another useful native HTML feature:

<form method="dialog"> 

When submitted through its submit button, this form can close its containing dialog rather than sending a normal network request. Do not confuse this behavior with saving a booking.

Important: In HAVEN Estates, this is only a frontend demonstration. The form does not actually send or store a viewing request. A production booking system would need server-side handling, validation, data storage, and user feedback.

8. Dialog vs. Popover: When should you use each?

Both are useful, but they solve different problems.

Feature Modal dialog Auto popover
Typical use Important focused interaction Dismissible supplementary UI
Makes background inert Yes, when opened modally No
Escape dismissal Supported Supported
Outside-click dismissal Not automatic by default Supported
Top-layer rendering Yes, when modal Yes, when shown
Example in HAVEN Property preview, viewing form Additional navigation

For HAVEN Estates, we use modal dialogs for detailed property previews and the viewing form, and a popover for additional navigation.

Choosing the right element improves the interaction before we even start styling it.

9. Common mistakes to avoid

Mistake 1: The IDs don't match

Incorrect:

<button commandfor="my-dialog" command="show-modal"> Open </button> <dialog id="another-dialog"></dialog> 

The button's target doesn't exist under that ID.

Correct:

<button commandfor="my-dialog" command="show-modal"> Open </button> <dialog id="my-dialog"></dialog> 

Mistake 2: Using the wrong command

These commands are not interchangeable:

command="show-modal" 

is intended for a dialog, while:

command="toggle-popover" 

is intended for a popover.

Mistake 3: Forgetting accessible names

A button containing only an icon can be difficult to understand without an accessible name.

Instead of:

<button commandfor="property-sereno" command="show-modal"> ↗ </button> 

Use:

<button commandfor="property-sereno" command="show-modal" aria-label="Quick view of Villa Sereno" > ↗ </button> 

Now assistive technologies can communicate its purpose.

Mistake 4: Assuming modern HTML works in every browser

Invoker Commands are modern platform features. Before using them in production, verify the browser versions your audience needs. Plan a fallback if unsupported browsers are part of your requirements.

Mistake 5: Confusing UI behavior with application functionality

Opening a booking form without JavaScript is not the same as implementing a booking system. Real business processes may still require JavaScript, a backend, authentication, persistence, and validation.

Watch the complete HAVEN Estates project

In the full video tutorial, I demonstrate:

  1. The completed luxury real estate interface.
  2. The starter project before commands are added.
  3. How to connect buttons to property dialogs.
  4. How to open and close dialogs without custom JavaScript handlers.
  5. How to build the navigation popover.
  6. How to control the viewing dialog.
  7. How to test the finished interactions.

Watch the complete YouTube tutorial:

Watch the HAVEN Estates HTML Commands tutorial

Improve your responsive CSS skills

If you want to become better at building polished layouts like HAVEN Estates, check out my guide:

Build Any Responsive Layout with CSS Grid & Flexbox

Final thoughts

Modern HTML keeps getting more powerful.

The command and commandfor attributes show how browsers can handle common interface interactions without requiring custom event listeners for every button.

That doesn't mean JavaScript is obsolete. It means we can use the browser's native capabilities first and add JavaScript when our application genuinely needs it.

Have you tried HTML Invoker Commands yet?

Would you use them for dialogs and popovers in your next project?

Let me know in the comments.

Happy coding!

— CodeWithDivine