material-tailwind icon indicating copy to clipboard operation
material-tailwind copied to clipboard

[W3C Compliance] Tailwind CSS Button with link is not W3C compliant

Open gustaveWPM opened this issue 2 years ago • 3 comments

Hello! I'm having a problem with the current implementation of buttons WITH links, concerning Material Tailwind.

The documentation states to do:

<a href="#buttons-with-link">
  <Button>Filled</Button>
</a>

(And it is ofc also possible to do):

<Link href="#buttons-with-link">
  <Button>Filled</Button>
</Link>

https://www.material-tailwind.com/docs/react/button#button-with-link

:information_source: It's a mistake to document React components using <a> for links, instead of <Link>, but that's another topic.

Using the code from the documentation presents an issue. The W3C standard prohibits nesting a <button> within an <a> (and vice versa).

In the case of <a> inside <button>, it's a very poor choice anyway because the clickable area is no longer the button but only its text, which is very silly.

In the case of <button> inside <a>, it's awkward because it seems like the most logical way to do it.

You can try it yourself here: https://validator.w3.org/nu/#textarea

By running the validator with the following code:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Lol</title>
    <meta name="description" content="Lol">
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <a href="#"><button>Click me!</button></a>
    <button><a href="#">Click me!</a></button>
  </body>
</html>

gustaveWPM avatar Sep 05 '23 09:09 gustaveWPM

I'm sorry, I tried to edit my message to correct my suggestions, including the "Second solution" option, which I thought was the correct one but turned out not to be, and so explain why... But since GitHub is incapable of properly coding a message form and message edits, I ended up with my edited message all messed up.

I will add my rationale below the original post of my issue, I'm just taking a break before rewriting it properly and submitting it below.

gustaveWPM avatar Sep 05 '23 20:09 gustaveWPM

Three solutions:

First solution (bad one)

Wrap the <button> within a <form>, using two attributes:

  • action, taking the URL,
  • method, equal to "get"
<form action="https://example.com" method="get">
  <button type="submit">Click me!</button>
</form>

This way to do has an advantage:

  • It preserves the initial style of buttons, including those directly dictated by the machine's OS.

And a big disadvantage:

  • If I write
<form action="https://example.com?lol=lulz&lulz=lol" method="get">
  <button type="submit">Click me!</button>
</form>

By clicking the button, I will be redirected to https://example.com! So, it's a poor choice when the URL contains parameters.

Second solution (seems good, but isn't)

Merge the <a> and <button> elements

Merge the <a> and <button> elements to create a <a>, styled as a button

<a class="button-style" href="#">Click me!</a>

This way to do has a big advantage:

  • All links will redirect to the good URL.

And a minor disadvantage:

  • We cannot inherit the OS buttons style.

I initially thought that the second solution was the good one, but it isn't. Here is one way to implement it:

  • Get the parent node with .parentNode on the <Button> node - https://developer.mozilla.org/en-US/docs/Web/API/Node/parentNode
  • Check if this is a <a> with .tagName (x.tagName === 'A') - https://developer.mozilla.org/fr/docs/Web/API/Element/tagName
    • Replace the parentNode, either with replaceWith, or by setting outerHTML. - https://developer.mozilla.org/fr/docs/Web/API/Element/replaceWith

But here's the catch:

Replace the parentNode, either with replaceWith, or by setting outerHTML. - https://developer.mozilla.org/fr/docs/Web/API/Element/replaceWith

I haven't found any way to do this properly with a new <Link> component, replacing the initial <Link>.


So, here is a third solution, which is simpler and probs the best way to do:

Third solution (finally the good one?)

Provide an optional argument

So we could conditionally render the <Button>, whether as a <Link to="_" target="_" {...{stylizedAsAButton}}>, whether as a regular <Button>.

Maybe it would be good to provide only a linkProps optional argument, as an object, and only check if linkProps and its to key are undefined (linkProps && linkProps.to) to know if we should render a <Link> or a <Button>. We can inject all the properties of linkProps with the spread operator.

Something like: <Link {...{linkProps}} {...{stylizedAsAButton}}>

gustaveWPM avatar Sep 06 '23 23:09 gustaveWPM

Well, NextUI provides something which is incredible to solve this problem: polymorphic components.

See: https://nextui.org/docs/components/link#polymorphic-component

gustaveWPM avatar Oct 25 '23 15:10 gustaveWPM