DEV Community

Cover image for CSS can help improve your HTML⁉ - Ep 2: buttons and links.
GrahamTheDev
GrahamTheDev

Posted on

CSS can help improve your HTML⁉ - Ep 2: buttons and links.

Part 2 of using CSS to make our HTML better!

In this second part of this series we will be exploring how we can use CSS to ensure (and enforce) that we are using <button> and <a> elements correctly...you would be surprised how many developers get this wrong!

If you missed part one you may need to have a quick read of that so you get the key concepts as I won't be repeating them in detail here:

All caught up? Great!

Before we start part 2, it is worth mentioning (reiterating) that this concept relies on two style sheets.

One for production (and development) and one for development only (the one containing error messages).

In the fiddles I have indicated which styles you would use in your production stylesheet and which to use in your development stylesheet!

Right, with that reminder out of the way, let's begin!

A button is a button!

I know I keep saying it and I sound like a broken record but, a button is a button!

See, I told you I keep saying it!

But what if we could guide our developers towards this logical conclusion using CSS?

Well first thing is first, we can make it so our .btn class only works on a <button>.

button.btn{}
Enter fullscreen mode Exit fullscreen mode

Try and use the .btn class on anything else and it just won't work!

but we can do better than that!

What if we can also give our team a little bit of a warning to let them know they have used the wrong element?

.btn:not(button){/* wrong element error highlighting */}
Enter fullscreen mode Exit fullscreen mode

Meaningful warning and error messages

Now while adding outlines to stuff that is wrong is great, it isn't very informative.

Luckily we can fix that also!

By using :after we can insert an error message

So we end up with a selector to add an outline and another selector to add a warning that is the same but using the :after pseudo selector.

By utilising the content property we can add a custom error message!

.btn:not(button) /* outlines */
.btn:not(button):after /* error message */

Enter fullscreen mode Exit fullscreen mode

Example for buttons

In this example we have a real button and a fake button made with a <div>.

Notice how the real button works and the fake button has no styling as well as an error message!

Taking it one step further!

I would argue that for most sites we can actually go one step further and completely do away with the .btn class on our buttons!

This helps with consistency, looks neater and enforces our policy of <button>s for buttons!

At this point we no longer even need an error check as we can only get styling on a <button>!

We can do this by using the element as the selector button{}!

button{
    padding: 0.75rem;
    background-color: #333;
    color: #fff;
    margin: 2rem 0;
    border: 0;
    border-radius: 0.3rem;
    width: 100%;
}
Enter fullscreen mode Exit fullscreen mode

However this may have one unintended consequence...

That's great, but we often style anchors like buttons!

And that is a valid point and nowadays, you are right!

However we can recycle our selector from the first part so we can have a .btn class only on anchors (<a>)!

button,
a.btn{/* our styles for buttons! */}
Enter fullscreen mode Exit fullscreen mode

This does mean we have to reintroduce our error checks though, but this time to ensure that we only use the .btn class on an anchor!

This raises an interesting question, do we want an error to show if we have a .btn class on a <button>?

If we do then our error selector just needs a subtle change

.btn:not(a){}
.btn:not(a):after{}
Enter fullscreen mode Exit fullscreen mode

This will add our error messages to <button class="btn", which might be confusing.

As it won't do any harm having the .btn class on a <button> (as we have no valid selector for it anyway) I would think it better to not show error messages in this instance.

Yet again the power of CSS makes this relatively simple, we can add multiple items within our :not selector!

.btn:not(a,button){}
.btn:not(a,button):after{}
Enter fullscreen mode Exit fullscreen mode

This is saying:-

  • select all items with the .btn class
  • that are not (:not())
  • an anchor (a)
  • OR a button ,button (comma seperated).

Example including anchors

Bringing everything together we end up with the following:

Looks great, but we can go even further!

But we only want valid anchors

Now that we have allowed the .btn class to be added to anchors we have opened up another problem!

The dreaded <a href="#" or <a href="javascript: void()!

This anti-pattern has not been needed since HTML5 (you did read my rant on buttons I linked earlier didn't you?).

So how can we fix this one?

Well in part 1 we used attribute selectors [attr] and once again they can come to our rescue!

As this is a site wide error (it should never be used!) we can ignore the .btn selector and just focus on anchors!

The first one is easier to solve, we select any anchors with a href="#" (and while we are at it an empty href or missing href too!)

a:not([href]), /* A missing href attribute! */
a[href=""], /* an empty (or null) href */
a[href="#"] /* the anti pattern! */
Enter fullscreen mode Exit fullscreen mode

That covers most of them, we just need to highlight the JavaScript anti-pattern!

Yet again we covered this in part 1, we can use the "begins with" notation:

a[href^="javascript:" i]
Enter fullscreen mode Exit fullscreen mode

Remember the "i" at the end to make it case insensitive.

We used the "begins with" ^= syntax for this example as it doesn't have to be javascript: void(), this way we can capture all JavaScript hrefs no matter what function they call!

Example for valid anchors!

Utilising the same system of using :after we can add messages to our anchors as well!

Note: We can make this much better by having separate message depending on whether the problem is a missing href, bad href or the use of JavaScript. I haven't done that in the example but you would do it as follows:

a:not([href]):after{content: "missing href attr"}
a[href="#"]:after{content: "anti-pattern on href detected"}
a[href=""]:after{content: "empty or null href"}
a[href^="javascript:" i]:after{content: "using javascript in href anti-pattern detected"}
Enter fullscreen mode Exit fullscreen mode

Additional things to mention

As pointed out by @ashleyjsheridan it is perfectly valid to have an anchor without a href.

As such where I marked anchors with missing or empty href attributes as errors this was a mistake on my part.

Instead these should be downgraded to "warnings" just to pick up on mistakes.

Additionally this then means that if you use <a name="sectionName"> for whatever reason it will only be a warning (although this is quite a dated way to do things as you can just give an element an id attribute and link to that instead).

Overall it has no bearing though as the development style sheet will not impact production, might just be a little annoying though if you do edge case stuff like dynamically adding a href to an existing anchor.

Additionally they mention:

Might also be worth considering the 3 button types: button, submit, and reset.

This is an excellent point, so to add those you would update the selector to target:

button,
a.btn,
input[type="button"],
input[type="submit"],
input[type="reset"]{
  /* can use the same default styling and overwrite with additional styles to differentiate as needed */
} 
Enter fullscreen mode Exit fullscreen mode

If you need to use those.

Conclusion

A lot of what we learned in part 1 has carried over to part 2.

It is just that our selectors have become a little more complex.

But hopefully you can see how this allows us to enforce business style guides once again (having or not requiring a .btn class on <button> elements for example).

In the next part we are going to focus on document structure best practices and introduce some WAI-ARIA attributes and some tips and tricks with those!

Thanks for Reading!

Thanks for reading, I hope you enjoyed it!

In fact to show my appreciation please do have a virtual ❤ and 🦄 to show how much it means to me!

If you enjoyed this and want something just a little (OK a lot longer I have written) why not read my monster accessibility tips article:

Have a great week!

Discussion (28)

Collapse
lukeshiru profile image
Luke Shiru

I have nothing against the actual article, I guess I have some thoughts against some "practices" that should be "deprecated" from my point of view, and I'm fine with them being "errors" instead of "warnings".

First: I know folks style anchors to look like buttons, but I would argue that you shouldn't do that at all. Why would you do something like this...

<a class="btn" role="button" aria-pressed="false">I'm a button, trust me</a>
Enter fullscreen mode Exit fullscreen mode

Instead of just:

<button class="btn">Don't trust the other button, I'm the real one</button>
Enter fullscreen mode Exit fullscreen mode

If the reason is: "I want it to look like a button, but I want it to redirect me to a new place" I have two possible answers:

  • Drop the "I want it to look like a button" and think better your design.
  • Use a button and the "click" event.

I think this issue generally comes from designers that just think about "a e s t h e t i c s" and they don't even consider a11y/UX. Generally the same kind of folks that don't consider i18n, responsiveness and other "variables" that directly affect the designs.

The other issue I have is with stuff like <a name="sectionName">. As you said it, that's a really outdated way of doing things, so marking it as an error is perfectly fine from my point of view. Yeah, I know is "valid", but eval is also a valid JS function and you should still avoid it. Is good to get "errors" when you're using outdated stuff.

That's it, sorry for "ranting" about this stuff, great post as always 💖

Cheers!

Collapse
sfiquet profile image
Sylvie Fiquet

I have a question as I'm still learning this stuff and my design skills are limited.

I agree that putting role=button on a link is just bad practice. If it's a link, it's a link.

My question concerns this bit:

If the reason is: "I want it to look like a button, but I want it to redirect me to a new place" I have two possible answers:

  • Drop the "I want it to look like a button" and think better your design.
  • Use a button and the "click" event.

I have an app where I did something like this on the homepage. Basically there's a link styled like a button to make it stand out more, with the idea of adding more as new functionalities are added.

It's definitely a link, screen-readers will treat it accordingly, and desktop users can see the url on hover.

Using a button would be bad for progressive enhancement, as it wouldn't work without JavaScript. And honestly it should be a link, I see no reason to use something else in this particular case.

Are you saying I should just choose a different styling to make my link stand out without looking like a button? My first question is why? Does it hurt users if a link is styled like a button? And assuming you're right, what would you suggest would be a better alternative?

Collapse
grahamthedev profile image
GrahamTheDev Author

You have almost certainly used the correct element so don't worry there.

The point they were trying to make is that it can be confusing for keyboard users when they encounter a link that looks like a button as they may press Space to try and activate it thinking it is a button (and that would just result in a page scroll normally!).

However it has become accepted practice to style "call to actions" etc. like buttons.

It will be fine as it is do not worry!

One suggestion would be to style your "links styled like buttons" slightly differently to "actual buttons" just so there is a visual difference / consistency across the site, but it is just a suggestion and certainly not something to worry about.

Thread Thread
sfiquet profile image
Sylvie Fiquet

This is exactly what I wanted to know. I had no idea about Space, I always use Enter on both links and buttons when testing on keyboard. I can see how that would be annoying.

Now I'm thinking about which styling would make it more obvious that it's a link. A different shape, colour or font wouldn't help, it would still suggest that it's functionally a button. My guess is that underlining on focus (like I already do on hover) would be the most obvious. Are there other widely used signals I should know about?

Thread Thread
grahamthedev profile image
GrahamTheDev Author

Underline on focus vs no underline on focus is one way. 👍

Iconography in conjunction with text is a great way to differentiate, an arrow pointing to the right for example would work for links to differentiate them from buttons.

The key here is something that is unique to buttons vs something that is unique to links styled like buttons just so a user can learn "square looking buttons are links, rounded looking buttons are actually buttons" for example.

Thread Thread
sfiquet profile image
Sylvie Fiquet

I like the arrow idea, it's a good hint.

The problem I have with two styles that users learn to recognise is that it might not work so great on a landing page, which would mainly be visited by new users rather than returning ones. But it's still good to know it's an option for a different use case.

Thanks for the tips!

Collapse
alvaromontoro profile image
Alvaro Montoro

I'm going to play devil's advocate here... how about a link to an external login site? A redirection to another site with a token and information, that will go back to our site eventually once it is validated. Should it be an <a> or a <button>? It is a redirection, so it seems like <a> would be a better option, but should it look like a button or like a link? After all, if it was an actual login form, it would be a <button>, no?

Collapse
lukeshiru profile image
Luke Shiru • Edited on

I guess could be a button inside a form with the action set to the target URL, and a bunch of hidden fields with the data you want to pass to the target URL. If you want to use JS, you could just do the redirect from the click event of said button.

Collapse
grahamthedev profile image
GrahamTheDev Author • Edited on

That's it, sorry for "ranting" about this stuff, great post as always 💖

I am the angry rants person, you are always welcome to rant on my posts, I enjoy it and it is useful! 😁

I agree with what you are saying here and in my own personal style sheet I would not include as much flexibility or use old stuff like <a name> or <input type="button" (although <input type="reset" I may occasionally reach for if I need a no JS form backup...can't remember the last time I did use it though 🤣).

But I want to show people options as the post is more about concepts and showing people how to "roll their own" solution, rather than a copy paste solution for everyone.

It is not my place (even if I like to pretend it is sometimes 😉) to tell people not to do something which is valid HTML even if I think it is outdated unless I am the one writing the cheques for the code!

As always we are pretty much on the same page about these things and perhaps my article was a little too "soft" this time! 😁❤

Collapse
cubiclesocial profile image
cubiclesocial

The problem with using <button>, especially the <input> button types, is that it comes with a LOT of baggage. Safari on iOS, for example, will override styles regardless of what they are with iOS button styles resulting in a less than attractive UI/UX. One could even argue that iOS buttons are downright ugly. a tags, on the other hand, can be styled however the designer wants. But even a tags also have baggage in that preventDefault() has to be called in Javascript (or return false; for the lazy) when handling the click action directly. Failing to do so causes the browser to add # to the URL and push the current page to the browser history back stack.

Collapse
grahamthedev profile image
GrahamTheDev Author

Safari on iOS, for example, will override styles regardless of what they are with iOS button styles resulting in a less than attractive UI/UX

button{
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
}
Enter fullscreen mode Exit fullscreen mode

And problem solved there and it has good support.

If this has been stopping you from using a <button> when you should then please start using them now.

Please read this article on why you must use a button as I would hope it will stop you using a pattern we haven't needed for a long time!

Thread Thread
emekaorji profile image
EmekaOrji

😂 I like this guy!

You solved it like "There's your answer, no more excuses"

Thread Thread
grahamthedev profile image
GrahamTheDev Author

Take no prisoners! 🤣

Collapse
emekaorji profile image
EmekaOrji

I believe website structure and layout design is improving by the year and we should not be so rigid as to follow a particular convention in a bid to honor some laid down rules that were given years ago.

Dev.to has always been known to be a tech writing/blogging platform until one day I saw a post by a regular writer raising money to buy a new laptop because his former laptop got broken.

I agree a "button is a button", I also rant about it in my head while building ever since I saw your post on it, but if we wrap ourselves, our team, our projects, start-ups or organization around that and not allow for flexibility, we wouldn't know what newer things we could create.

The designer who began the trend of making links look like buttons must have sat to think "who said only buttons could look this way". The look of buttons were also created by a person 😒.

In most documentation today, the href='#SectionOfPage' used on an anchor tag (while passing the id='SectionOfPage' into the section tag), is used to navigate between sections of pages. That is a button-like functionality there, but still we use it to navigate faster, plus in few cases where JavaScript doesn't load, navigation still works.

The web as we know it would have changed a lot in less than 50yrs from now all because we explored possibilities.

Yes @inhuofficial I learnt a bunch from your post! This is so useful in a team/organization context. Where everyone has to write in a conventional pattern that the whole team understands.
🚀

Collapse
grahamthedev profile image
GrahamTheDev Author

Layouts I totally agree on! Semantics should be followed as there is very very little that you cannot do while following best practices! ❤️

Collapse
ashleyjsheridan profile image
Ashley Sheridan

Link tags are allowed to have missing or empty href attributes in the case that their resulting action would be not allowed. The idea is that the page would consist of dynamic content, but the link is left in as an effective placeholder. Assistive tech following the spec will ignore these links, but they're still perfectly valid. The links are, effectively, in a disabled state.

Might also be worth considering the 3 <input> button types: button, submit, and reset.

Also, while lesser used, there are other patterns that apply button styles to elements, such as <label>s attached to radio buttons and checkboxes, with the corresponding role and aria- attributes added for better accessibility. These sorts of markups are infrequently used, but they're still valid.

Collapse
oskargrosser profile image
Oskar Grosser • Edited on

I did not know that href may be left out to let anchor elements behave as placeholders, thank you! I should definitely read the specs more instead of relying on tools' complaints (i.e. Lighthouse lead me to believe that missing the href attribute is invalid or inaccessible HTML).

Collapse
ashleyjsheridan profile image
Ashley Sheridan

Lighthouse is not really that good for accessibility testing, it misses a lot and throws up a fair few false positives. The a11y tool in Firefox is better, but better than both of those is the aXe tool, which is available as a plugin for all the major browsers.

Collapse
grahamthedev profile image
GrahamTheDev Author • Edited on

Some great points here, I will see about adjusting the article and examples to account for these and downgrade them to warnings rather than errors.

I can’t see a valid use case for applying button styles to labels though as those would effectively be custom controls at that stage (so I would argue a separate class for clarity - even if it does go against DRY principles?) what do you think?

Update

I added a new section to account for the items mentioned here.

See if you think that covers it well enough or if there is anything else to add, I will work on updating the fiddles to incorporate that.

Thanks for the suggestions! ❤

Collapse
ashleyjsheridan profile image
Ashley Sheridan

My point about the <a> without an href attribute wasn't about anchors, but still links. Imagine something like this:

<a>You can find your purchased download here.</a>
Enter fullscreen mode Exit fullscreen mode

This would create the link as a placeholder, which might be useful for visual reasons, but it wouldn't yet be an active control. Then, based on some sort of event, you could make it active by giving it a valid href value using Javascript.

It's not a common pattern, but it's a completely one.

Thread Thread
grahamthedev profile image
GrahamTheDev Author

I used "anchors" for all <a> elements, perhaps the wording wasn't clear but I was talking about <a name="" would then be a warning in addition to allowing for <a>some text</a> to be a warning rather than an error (just in case you did want to do something like you suggest).

I think we are in agreement! lol!

Collapse
posandu profile image
Posandu 🚀

Great Article!
PS: I Codepen down? I can't see the embed

Collapse
grahamthedev profile image
GrahamTheDev Author • Edited on

Looks that way! Never seen CodePen go down before 😱

Edit: looks like AWS has gone belly up...AGAIN...so much for their reliability as that is twice in a month!

Collapse
posandu profile image
Posandu 🚀

Okay. thanks for the reply :)

Collapse
charliejoel profile image
Charlie Joel

This is actually a pretty good idea! It could finally stop junior devs from using divs as buttons all the time. Might add this into the SCSS boilerplate at work...

Collapse
grahamthedev profile image
GrahamTheDev Author

Steal it, use it, save developers from themselves! 🤣

Glad you found it useful! ❤

Collapse
nefofortressia profile image
Nefo Fortressia • Edited on

This is very cool! Though it's more practical to enforce it trough code style enforcement tools like ESLint or other similar lint tools since you can integrate it with CI.

Collapse
grahamthedev profile image
GrahamTheDev Author

Part 3 is where this starts to get really interesting, so make sure to follow me for that!

If I missed anything that we could check for then let me know once again!