1. Web Design
  2. CSS

Mastering General Sibling Selectors: Custom Tab Navigation


Welcome to the next in our series of tutorials where we use the general sibling combinator ~ to create various components for the web. This tutorial will cover navigation elements using links and radio inputs.

In addition to CSS selectors, the will-change property, and input states from the previous tutorial, we’ll also cover a border hack, Sass for loops, calc(), and accessibility!

What We’re Working Towards

Here’s the demo we’ll be building:

A quick disclaimer: these CSS effects may or may not work in older browsers–I’ve tested them in the latest versions of Chrome, Firefox, and Safari.

I'll be using Sass, a CSS preprocessor to speed up the coding process!

I’ll also be using the amazing AutoPrefixer instead of vendor prefixes. If you use CodePen, be sure to go to your pen’s settings, click on CSS, and select AutoPrefixer.

1. Link Tabs

The first version we’ll be creating is the tab bar comprising <a> links. This is the most straightforward and easy to make; it’s ideal for a general navigation to be used on multiple different web pages.

Setting Up the HTML

We’ll start with a basic <nav> element with five links and a border. You can also use a different container element, but I prefer a navigation element since that’s the purpose of this snippet. The border serves to show which link is being hovered over or selected.

I also wrap my navigation in another container, but you don’t need that for the basic markup!

Styling the Tabs

The next step is to create a visual base for the tabs. The container should be relatively positioned; we’ll use flexbox to position the links on one line, but you can also use floats. The links should take up an equal amount of space, filling up the entire width of the container.

This part can be tricky, so you should adjust it based on your use case. I knew I needed exactly five links for this demo, so I was able to use 5 as a Sass variable $n in order to calculate the exact width calc(100% / 5). I did this because I wanted to be able to easily and quickly change the link number at the top of my stylesheet, but you can also use a straight percentage 20% if you prefer not to use calc(), or a fixed width 160px if you don’t know how many links you’ll have.

The border should be the same width as each link–we’ll use the same width we used for the links. Lastly, we’ll position it absolutely at the bottom left corner of the container.

Adding Functionality

The next part uses the general selector! We’ll be styling :hover, :active, and :focus, as well as an .active class if you’d like to use JavaScript to make the border “stick” once you click a link.

Every time you hover over a link, the border needs to move to the position of the link. If each link is 160px, then hovering over the second link causes the border to move 160px to the right; hovering over the third link should cause the border to move 320px to the right.

Using this information, we can build a Sass for loop that calculates the numbers automatically. This is a directive that outputs a set of styles a certain number of times–in this case, it would output this transform style $n times, or 5, as we specified earlier.

$i is the number the loop is currently on: 1, 2, 3, 4, or 5. In order to use this number inside the loop, we’ll have to escape it by wrapping it with a pound sign and curly brackets #{}.

If we were to use fixed widths, we would replace 100%, which moves the border by its full width, with 160px or however wide the links need to be. If you end up using .active, you will need to use specify ~ hr like in the example above because otherwise the border will stick to the active position.

You can also do this without a for loop, but it’s not as easy to update or adjust. The second link (a:nth-child(2)) moves translateX(100%), the fourth link moves translateX(300%), etc. You will need to specify a transform for each link in your navigation.

If you hover over the Sass code in the example above, a View Compiled button should appear in the bottom right corner that you can click to view the compiled CSS. This is useful for seeing how the for loop works and how you can achieve the same without this Sass directive.

Adding Animation and Transitions

Finally, we’ll add some transitions to create the border “sliding” motion from link to link. We’ll add a slower transition to the border itself for when it returns to the default position and a faster transition to the border on hover so that it snaps to the correct link. The last step is to add some color changes to the border on hover and on click!

2. Radio Tabs

This next version of the custom tab navigation looks identical to the first, but uses radio inputs instead of links. This works well when navigating through content sections on the same page!

Setting Up the HTML

The main difference in the HTML is that we now need two elements per tab: a label to visually represent the tab and a radio input to add functionality.

Each radio input has the same name so that selecting one will deselect the others. The labels correspond to each input’s ID. You can also organize your radio inputs and labels so that corresponding labels and inputs are next to each other if that structure is preferable!


Styling for the radio tab nav is almost exactly the same as the link tab nav. The only difference is that each radio input needs to be absolutely positioned directly on top of its corresponding label. I used a Sass for loop to avoid repetition, and also used calc() to avoid percentage decimals such as 16.6667%.

Adding Functionality and Animation

For the transforms, instead of having an .active class, we’ll use :checked. This state is useful because now the border will “stick” when you select one of the radios!

3. Arrow Tabs

Arrow tabs are functionally the same as the radio tabs; they only differ visually. This version is good for breadcrumb-style or step-by-step content. You can also use links if you prefer!

Setting Up the HTML and Styling the Tabs

The HTML setup is the same as the radio input tabs above. If you want to use links, it will be the same as the link tabs.

The biggest difference in styling comes from the border that moves on hover. Instead of a thin line on the bottom, the border instead takes up the entire height of the navigation. It also gets positioned behind the inputs and labels.

To create the pointed ends, we’ll use a border hack on hr:before and hr:after. Each pseudo element will have a height and width of 0, but a border-width that makes it as tall as the parent container. This creates triangles that we can adjust individually to create our pointed ends.

For the left set of triangles, the left border should be white while the others are colored; for the right set of triangles, the left border should be colored while the others are white. We’ll also set the left and right borders of both sets to be thinner than the top and bottom so that the edges look shorter. Then we’ll position each set on the left and right sides of hr.

Adding Functionality and Animation

The functionality and animation are exactly the same as the radio inputs, or links if you used them instead.

ARIA and Accessibility

While these navigation elements work well from a visual standpoint, somebody using a screen reader or who needs other forms of accessibility may have trouble reading or accessing each option.

To remedy some of these issues, we can use a combination of roles, labels, hidden elements, and tabindex attributes so that the navigation reads properly from an accessibility standpoint. I’m still learning a lot about accessibility, but this combination seems to work best with this style of navigation.

I mainly focused on hiding elements that aren’t necessary for screen readers while labelling elements that are important to the nav’s functionality, such as the radio inputs that don’t include text by default like links do. Try what works best for your use case!


There are dozens of different ways to utilize the general sibling selector that are visual, functional, or both. It provides a powerful way to customize components without having to use more than just CSS and HTML. We’ve covered form and navigation elements so far; in the next one, we’ll learn how to make a custom dropdown. Feel free to drop a comment below if you have any questions or feedback!

Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.