Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use button instead of a for closing modals #446

Closed
Atmos4 opened this issue Feb 13, 2024 · 14 comments · Fixed by #453
Closed

Use button instead of a for closing modals #446

Atmos4 opened this issue Feb 13, 2024 · 14 comments · Fixed by #453

Comments

@Atmos4
Copy link

Atmos4 commented Feb 13, 2024

Describe the issue

The usage of button for closing modals makes a lot more sense than a.

  • from w3:

The button element should be used for any interaction that performs an action on the current page. The a element should be used for any interaction that navigates to another view.

  • from UX: Links only respond to Enter key presses. Buttons respond to both Enter and Space.
  • Links need to have an href attribute in order to be accessible. This forces the usage of href=#, which can confuse screen readers and will in fact perform the scroll animation unless the onclick handler returns false, which further increases verbosity.

Solutions

One solution would be to recommend button as a way to close modals, and properly style any button with class=close (or rel=prev even though it is not valid on buttons, and also a non-standard way to describe dialog closing actions).

Extending the previous solution for classless pico, we can use <form method=dialog> HTML element, which is a non-Javascript solution to closing dialogs. Alternatively if the button is already in a form, we can use <button formmethod=dialog>.

Here is a demo of the extended solution on CodePen.

If you agree on the issue, let me know which solution you like best and I can start working on a PR!

@Atmos4 Atmos4 changed the title Anchor tag to close modals is non standard Use button instead of a for closing modals Feb 13, 2024
@lucaslarroche
Copy link
Member

lucaslarroche commented Feb 13, 2024

@Atmos4,
Thank you. I appreciate your research. I agree. You made a good point.

But we must avoid breaking changes because we are no longer in beta or release-candidate.
So the current <a rel=prev></a> should still be functional.

I would prefer formmethod="dialog" (vs rel="prev").
I didn't know about HTMLDialogElement, so that's even better.

Sure, it would be great if you could open a PR.
Also, as following PRs, we would need to update the website and the examples.
It can come later.

Note, you also need to reset box-shadow

(EDITED)

@Atmos4
Copy link
Author

Atmos4 commented Feb 13, 2024

@lucaslarroche
I will work on a PR as soon I have some free time!

Out of curiosity, how much does bundle size matter to this project? If it is important, then I might just go with the first solution I described. It should be doable with :is(button, a):has([rel=prev]).

I edited the codepen to reflect what I described above, and simplify the CSS to work with .close class instead.

@lucaslarroche
Copy link
Member

lucaslarroche commented Feb 13, 2024

Semantically valid is more important than the bundle size.

:is(button, a):has([rel=prev])

Agreed

(EDITED)

@Atmos4
Copy link
Author

Atmos4 commented Feb 15, 2024

PR is ready, see #448

@lucaslarroche
Copy link
Member

@Atmos4, I edited my comments about targeting <button formmethod="dialog" />
I was confused with your example using it, but dialog is not a valid formmethod.

So you were right, .close, :is(a, button)[rel=prev] and :is(a, button)[rel=prev] are good!

@lucaslarroche lucaslarroche mentioned this issue Feb 16, 2024
@lucaslarroche lucaslarroche linked a pull request Feb 16, 2024 that will close this issue
@Atmos4
Copy link
Author

Atmos4 commented Feb 17, 2024

dialog is not a valid formmethod.

@lucaslarroche it is! Check out the MDN article and this codepen. The original codepen I posted above was also containing this solution.

@lucaslarroche
Copy link
Member

@lucaslarroche it is! Check out the MDN article and this codepen.

Great. I missed it when researching yesterday.

The original codepen I posted above was also containing this solution.

Yes. That's why I suggested at some point to target button[formmethod] to avoid using rel.

@Atmos4
Copy link
Author

Atmos4 commented Feb 18, 2024

@lucaslarroche The problem with using button[formmethod] as the way to style the icon is that there are other valid use cases for button[formmethod] without the icon, for example:

<dialog>
  <article>
    <!-- Modal content -->
    <footer>
      <form>
        <button formmethod=dialog>Cancel</button> <!-- this button should look normal! -->
      </form>
    </footer>
  </article>
</dialog>

By the way, is there a reason to have some part of the icon specific code coupled to the header? It could be interesting to allow usage of that icon anywhere inside the article, for example like this:

<dialog id=modal1>
  <article>
     <button rel=prev onclick="modal1.close()"></button> <!-- currently doesn't float:right -->
    <!-- Modal content -->
  </article>
</dialog>

I can create an issue/PR to decouple the code if you are interested. Maybe there are valid reasons to have it otherwise though!

@micchickenburger
Copy link

I think there are many problems with using rel=prev. Besides being an invalid attribute, the property can't be set when interacting with the DOM. For instance:

const closeButton = document.createElement('button');
closeButton.ariaLabel = 'Close';
closeButton.rel = 'prev'; // pico uses this attribute, even though it's invalid

Typescript will throw an error: Property 'rel' does not exist on type 'HTMLButtonElement'. ts(2339).

Furthermore, executing the code in the browser (at least in Safari) will not set the attribute in the DOM, making for a funny dialog:

image

I think whatever solution is chosen should be compatible with the specification, else issues will arise for those of us who generate code from scripts.

Thanks for this awesome project!!

@Atmos4
Copy link
Author

Atmos4 commented Jul 10, 2024

@micchickenburger thank you for your interest in the project.

I think you are confused between DOM properties and HTML attributes. Technically you can set any attribute on any HTML element using the setAttribute method. DOM properties work very differently, so I suggest you read up on the subject.

As of the Safari issue, it works on my iOS Safari. Can you send a link to a CopePen / Stackblitz?

@micchickenburger
Copy link

Hi @Atmos4, thanks for your reply! I know that arbitrary attributes can be set on elements, but I think this is a bad practice. Therefore, I only use the DOM for programmatically setting attributes. It ensures compliance with the HTML standards.

For instance, rel is not a valid property on the button element. That attribute is only valid on a, link, area, and form elements: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel

Setting what appears to be a valid attribute with a valid property (ie prev) on a button element may result in unexpected behavior in different environments. That's not to say it couldn't work; I am merely suggesting that we should remain strictly compliant with the standards. :)

@Atmos4
Copy link
Author

Atmos4 commented Jul 22, 2024

I know that arbitrary attributes can be set on elements

Good! It sounded like you didn't :) which is fine, you don't need to know everything

I think this is a bad practice

Then you are one of a kind: it's actually very common. From the (somewhat standard) use of dataset to unocss attributify mode and everything in-between (dusk selectors, htmx, alpineJS, the list goes on). Custom attributes are not only very common, they are also super useful!

But this isn't a custom attribute, it's actually an official one that you normally don't use on buttons. Which, in my honest opinion, is also completely fine. It would actually be fine using an anchor tag here since modals are sometimes considered to be valid members of the history, same as actual pages.

For instance, rel is not a valid property on the button element. That attribute is only valid on a, link, area, and form elements

You are definitely confused between attribute and property :D it's really not the same thing!
I don't think this is a big issue, but you have all the right to find this annoying. You are welcome to use an anchor tag a instead with href=# - be careful, if you don't specify href the link will behave in a very weird way.

Note: I really wonder why you would want to create an HTMLButtonElement from Javascript 🤔

@micchickenburger
Copy link

@Atmos4 Thanks for your reply. I believe I correctly used attribute and property. In the former case I was referring to the HTML element. In the latter case I was referring to the HTMLButtonElement instance in JavaScript, though I did incorrectly call the instantiated object an element. In any case, this is becoming a bit pedantic.

Said correctly, rel is not a valid property of HTMLButtonElement. I was not implying that the use of arbitrary attributes in HTML was bad practice. I was conveying that I think it's bad practice to use an existing attribute that is specifically defined to not apply to the button element. It may cause inconsistent behavior across browsers who may want to do something with rel without first checking the element tag. I did choose to use an anchor element.

To interact with a button element that is not defined in the HTML, one must create it. This can be done in the DOM, as I have, or as an inline HTML string appended to an existing element in HTML. The latter I think is worse because I believe it requires a complete repaint, and must be eval'd. In other words, if the string could be manipulated in any way, ie XSS or in memory, there is no structure or inherent sanitation that would occur.

@Atmos4
Copy link
Author

Atmos4 commented Jul 22, 2024

To interact with a button element that is not defined in the HTML, one must create it

Why wouldn't you define it in the HTML? Why would you create it with vanilla JS? So many questions :D

I did choose to use an anchor element

Excellent 👌

rel is not a valid property of HTMLButtonElement

You are very stubborn :) for the final time, rel is not a property

Side note: the only reason why rel is not part of the button's official attribute list is because buttons are generally underpowered and HTML as a standard evolved away from the original representational state transfer proposal, which is very sad and which I would very much like to change! Here is an excellent conference explaining some part of that problem if you are interested: https://www.youtube.com/watch?v=inRB6ull5WQ

Anyways, I think we are done with the topic and it's very much down to opinions and knowledge of HTML. Going back to the original question:

  • the error you see is because rel is not part of the reflected attribute list of the button element. However, there is no such thing as invalid attribute. Any html element can have any attribute, only some of them have an effect.
  • if you want to set attributes in JS, you can do so with setAttribute. Doing this should fix your styling issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants