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:
Article No Longer Available
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!
Article No Longer Available
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{}
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 */}
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 */
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%;
}
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! */}
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{}
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{}
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! */
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]
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 href
s 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"}
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 */
}
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:
Article No Longer Available
Have a great week!
Top comments (26)
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:
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?
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.
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?
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.
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!
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 theid='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.
🚀
Layouts I totally agree on! Semantics should be followed as there is very very little that you cannot do while following best practices! ❤️
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
, andreset
.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 correspondingrole
andaria-
attributes added for better accessibility. These sorts of markups are infrequently used, but they're still valid.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! ❤
My point about the
<a>
without anhref
attribute wasn't about anchors, but still links. Imagine something like this: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.
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!
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 thehref
attribute is invalid or inaccessible HTML).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.
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?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! 😁❤
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 evena
tags also have baggage in thatpreventDefault()
has to be called in Javascript (orreturn 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.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!
😂 I like this guy!
You solved it like "There's your answer, no more excuses"
Take no prisoners! 🤣
Great Article!
PS: I Codepen down? I can't see the embed
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!
Okay. thanks for the reply :)
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...
Steal it, use it, save developers from themselves! 🤣
Glad you found it useful! ❤
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.
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!