Note: This article is one of a series demonstrating building a React navigational component from scratch while considering accessibility through the process. The articles are accompanied by a GitHub repository with releases tied to one or more articles; each builds on the previous one until a fully implemented navigation component is complete.
Each release and its associated tag contain fully runnable code for the article. The code discussed in this article is available in the release. and may be downloaded at release 0.9.0. A page showcasing these base components may be run locally through this release.
While code examples are written in JavaScript for brevity, all actual code is written in Typescript and targets React 19.x, all while using vanilla CSS. Examples use Next.js v16.x, which is not required to run the navigation component.]
The design requirements for this release are available.
-—
Content Links
Introduction
With only one more release coming up and the horizontal layout styling complete, now is the time to focus on completing vertical layout styling. Most of the layout itself was completed in an earlier release and described in the article Laying it all out on the vertical.
When styling a component, care must be taken to ensure parity with the information provided by screen readers through some aria attributes. For instance, a navigation component must set the aria-current="page" attribute on a link whose href points to the current page. In the example being shown, the link in question is the "by Era" link, under Find Your Next Story and Tales.
While screen reader users hear which button or link is the item currently capturing focus, styling is needed to achieve the same for screen users. Visual styling is necessary to guarantee that visual and auditory/tactile users have the same information.
Layout
@layer system-component {
div.vertical {
position: relative;
}
nav.vertical-navigation {
min-width: calc(var(--sp-px) * 320);
position: absolute;
top: calc(var(--sp-px) * 24);
width: 30%;
z-index: 3;
/* Layout */
& > ul {
& > li {
& > button,
& > a[href] {
justify-content: flex-start;
}
/*sub navigation (not top row)*/
& > ul {
& li {
width: 100%;
}
& > li {
& button,
& a[href] {
flex-wrap: nowrap;
justify-content: flex-start;
}
}
}
}
}
}
}
GitHub (release 0.9.0) - styledVertical.css
The earlier layout has been moved to the top of the style sheet, and all padding has been removed; a width of 30% is set, with a minimum width constraint of 320 relative pixels. The entire nav element is set to relative positioning. While some media queries regarding size would be useful here, I'm going to forgo them in this styling context. The nav element is absolutely positioned within a containing div that is relatively positioned and shifted down slightly to create more white space between the description and the nav element.
With the layout complete, attention turns to styling the nav element.
Appearance
<nav / >
/* Appearance */
/* nav */
nav.vertical-navigation {
border-color: var(--purple-7);
border-style: solid;
border-width: calc(var(--sp-px) * 1);
box-shadow: calc(var(--sp-px) * 7) calc(var(--sp-px) * 7) calc(
var(--sp-px) * 7
) oklch(var(--raw-purple-4) / 0.4);
padding: calc(var(--sp-px) * 8) calc(var(--sp-px) * 16);
padding-left: calc(var(--sp-px) * 20);
}
Styling the nav element with borders visually distinguishes the nav component and groups the nested lists. A box shadow is applied to the right and bottom to create the illusion of depth.
Generic Styling
List items, buttons, and links need some generic styling, much of which was applied in the earlier version.
& li {
border-color: transparent;
}
& button,
& a[href] {
background-color: transparent;
border-color: transparent;
border-bottom-color: var(--purple-3);
border-radius: 0;
border-style: solid;
border-width: calc(var(--sp-px) * 1);
border-left-width: calc(var(--sp-px) * 2);
display: flex;
font-weight: 400;
padding: calc(var(--sp-px) * 4);
text-decoration: none;
white-space: nowrap;
width: 100%;
&:focus-visible {
outline: none;
}
}
Border colors are removed on the list items. List items cannot display borders since a list would show a border for all the elements in a nested list along with the button and that's not the styling I'm attempting to achieve.
Buttons and links are styled together. Since everything will be clickable, text decoration is removed, while font weight and base padding are standardized. A width of 100% is applied to ensure the focusable element fills the list item it is contained in. This again ensures the target area is at least 24 relative pixels wide, while the target height is determined by the font-size (16 relative pixels) plus the 4 relative pixels of padding applied to the top and bottom.
Reimagining Focus and Hover
& button,
& a[href] {
… &:focus-visible {
outline: none;
}
&:hover,
&:focus {
border-left-color: var(--purple-7);
}
}
A focus outline can look terrible when focusable elements are part of a list, so the outline is set to none, and further focused styling needs to be applied.
Adding color to the left border to the focusable elements allows the user to know where the cursor or target pointer is. This again achieves parity with the information coming from a screen reader which announces each link or button as it attains focus.
Achieving Parity
aria-current="page"
As stated earlier, a screen reader can announce to a user when a link references the page the user is currently visiting. To achieve parity, a visual cue should be styled to inform a screen-focused user of the same.
nav.vertical-navigation {
... & a[href][aria-current="page"] {
&:before {
color: var(--purple-8);
content: "\25C9";
position: absolute;
left: calc(var(--sp-px) * 4);
}
}
}
Any link in the navigation may include the aria-current attribute, so styling is applied by placing the selector directly under the nav element. A fisheye unicode character is applied, and positioned 4 relative pixels to the left of the link element. Even though the visual is associated with the link, the character is positioned outside of the element.
Refinements
With almost all the styling completed, it's time for some refinements, specifically, the bottom border under the About button should be removed, and sublists should be indented without affecting the current left border.
nav.vertical-navigation {
...
& > ul > li {
&:last-child {
& > button,
& > a[href] {
border-bottom-color: transparent;
}
}
& ul > li {
& button,
& a[href] {
padding-left: calc(var(--sp-px) * 16);
}
& > ul > li {
& button,
& a[href] {
padding-left: calc(var(--sp-px) * (16 * 2));
}
}
}
}
}
The bottom border only needs to be removed from the last child in the top set of navigational elements. In keeping with the requirements to reduce shifting, the border bottom color is set to transparent rather than removed altogether.
Because I want the left border to remain in place, I have to shift the left padding of a focusable element, and this must be done at every level of the subnavigation.
With this last code in place, the vertically styled navigation is complete!




Top comments (0)