Add a Skip Link Utility as a Tailwind Plugin

5 min read

In an effort to start filling in some accessibility gaps, we're going to add a skip link to our site. This link will be the first element to receive focus and clicking it will jump the user past our header to the content area.

We'll implement this as a TailwindCSS Plugin that we'll define right in our tailwind.config.js file.

Initial Plugin Setup

In order to create this plugin, we'll start by importing the plugin function from tailwind into our tailwind config.

tailwind.config.js

+const plugin = require('tailwindcss/plugin')

module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}',
    './content/**/*.md',
  ],
  theme: {
    extend: {
      gridTemplateRows: {
        3: 'auto 1fr auto',
      },
    },
  },
  plugins: [
    require('@tailwindcss/typography'),
    require('@tailwindcss/line-clamp'),
  ],
}

Now in the plugins array, we can add our plugin function.

plugins: [
  require('@tailwindcss/typography'),
  require('@tailwindcss/line-clamp'),
+ plugin(function ({ addComponents }) {
+   addComponents([
+
+   ])
+ }
],

We're destructuring the argument passed into the plugin callback and grabbing the addComponents function. Then inside the callback, we'll call addComponents with an array. This array defines each component as an object.

Let's define that object now. The following object will be the only element in that addComponents array.

{
  '.skip-link': {
    position: 'absolute',
    transform: 'translateY(-100%)',
    '&:focus': {
      transform: 'translateY(0%)',
    },
  },
},

Here, we're defining a CSS class called skip-link that absolutely positions an element at the top of our page, shifts it up out of the view, and then resets the transform when the element is focused.

Your Tailwind config should look like this now.

const plugin = require('tailwindcss/plugin')

module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}',
    './content/**/*.md',
  ],
  theme: {
    extend: {
      gridTemplateRows: {
        3: 'auto 1fr auto',
      },
    },
  },
  plugins: [
    require('@tailwindcss/typography'),
    require('@tailwindcss/line-clamp'),
    plugin(function ({ addComponents }) {
      addComponents([
        {
          '.skip-link': {
            position: 'absolute',
            transform: 'translateY(-100%)',
            '&:focus': {
              transform: 'translateY(0%)',
            },
          },
        },
      ])
    }),
  ],
}

We'll come back and apply a bit more styling to this in a bit, but this is enough for use to add it to our site and try it out.

Adding the Markup

We're going to go into components/MainLayout.tsx and at the top of our header element, we'll add a link and apply our new skip-link class to it. We'll set the href to #main-content, and then update the main element in our layout so that main-content is the id attribute for that element.

<header>
+ <a href="#main-content" className="skip-link">
+   Skip to main content
+ </a>
  <div className="py-6">
    <span className="text-5xl font-extrabold text-sky-800">
      My Awesome Blog
    </span>
  </div>
  <nav>
    <NavLink href="/">Home</NavLink>
    <NavLink href="/blog">Blog</NavLink>
  </nav>
</header>
-<main>{children}</main>
+<main id="main-content">{children}</main>

With this in place, you can hit your tab key from the browser's address bar, and the skip link should be visible and focused. You can hit enter and it should jump to the main content area.

Note
  1. If you have Chrome's DevTools open, they will get focus before your page, so you'll want to close those for this test.
  2. You might not notice the jump unless you're on a page with enough content to scroll, so try it out from one of the sample posts with a decent amount of content.

Make it Look Nice

We've satisfied our need for a skip link, but there's no reason not to make it look nice. Let's see how you can tap into the Tailwind theme and apply some styling in this plugin.

Back in tailwind.config.js let's add the theme() function to our destructured plugin callback arguments.

plugins: [
    require('@tailwindcss/typography'),
    require('@tailwindcss/line-clamp'),
-   plugin(function ({ addComponents }) {
+   plugin(function ({ addComponents, theme }) {
      addComponents([

This theme function allows us to look up values from the currently configured theme. This way, if we override a default value in the theme, it'll carry through to our plugin.

Let's throw some additional styling into our plugin and then talk through the values.

plugin(function ({ addComponents, theme }) {
  addComponents([
    {
      '.skip-link': {
        position: 'absolute',
        transform: 'translateY(-100%)',
+       padding: theme('spacing.1'),
+       borderRadius: theme('borderRadius.md'),
+       color: theme('colors.sky.900'),
+       fontWeight: theme('fontWeight.semibold'),
+       textDecoration: 'underline',
+       backgroundColor: theme('colors.gray.100'),
+       borderWidth: theme('borderWidth.2'),
+       borderColor: theme('colors.sky.900'),
+       transition: 'transform 0.3s',
        '&:focus': {
          transform: 'translateY(0%)',
        },
      },
    },
  ])
}),

I've thrown some padding, borders, and text styling on here. You'll notice that several of the values are making use of the theme function. The theme path of the desired value is passed to theme as a string. In order to determine what those values are, I referenced the structure of the default tailwind theme here.

Now when we tab from the address bar, we should see a more styled skip link slide in with a subtle animation. You can, of course, style this as much or as little as you want for your own needs.

Wrap Up

By doing this via a tailwind plugin, we've created our custom styling without opting out of the theming capabilities offered by using Tailwind. While we defined this one inline, we could easily extract it and publish it via npm if we needed to use this in multiple projects.

We've improved the accessibility of our site, and we've learned how straightforward it can be to create a tailwind plugin to add custom capabilities.

Hot Tip
Check back next week for the next article in the series, Add Page Titles and the lang Attribute in a Next.js App!