DEV Community

Cover image for Styling the Vertical - Achieving Parity
ShaynaProductions
ShaynaProductions

Posted on

Styling the Vertical - Achieving Parity

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.

Vertical Styling Base Image, with a green arrow pointing to the link representing the current page, by Era.)

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;
            }
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

Thin purple borders surround the nav element with box shadowing applied to the right and left.

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

An animated gif displaying the left border changes on focus and hover

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);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

Image of the navigation component displaying the visual indicator to the left of the By Era link, indicating the link is equal to the current page.

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));
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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!

An animated gif showing the completely styled nav component. Nested sublists are indented.

Summary

Top comments (0)