Tag: Content

An Interactive Starry Backdrop for Content

I was fortunate last year to get approached by Shawn Wang (swyx) about doing some work for Temporal. The idea was to cast my creative eye over what was on the site and come up with some ideas that would give the site a little “something” extra. This was quite a neat challenge as I consider myself more of a developer than a designer. But I love learning and leveling up the design side of my game.

One of the ideas I came up with was this interactive starry backdrop. You can see it working in this shared demo:

The neat thing about this design is that it’s built as a drop-in React component. And it’s super configurable in the sense that once you’ve put together the foundations for it, you can make it completely your own. Don’t want stars? Put something else in place. Don’t want randomly positioned particles? Place them in a constructed way. You have total control of what to bend it to your will.

So, let’s look at how we can create this drop-in component for your site! Today’s weapons of choice? React, GreenSock and HTML <canvas>. The React part is totally optional, of course, but, having this interactive backdrop as a drop-in component makes it something you can employ on other projects.

Let’s start by scaffolding a basic app

import React from 'https://cdn.skypack.dev/react' import ReactDOM from 'https://cdn.skypack.dev/react-dom' import gsap from 'https://cdn.skypack.dev/gsap'  const ROOT_NODE = document.querySelector('#app')  const Starscape = () => <h1>Cool Thingzzz!</h1>  const App = () => <Starscape/>  ReactDOM.render(<App/>, ROOT_NODE)

First thing we need to do is render a <canvas> element and grab a reference to it that we can use within React’s useEffect. For those not using React, store a reference to the <canvas> in a variable instead.

const Starscape = () => {   const canvasRef = React.useRef(null)   return <canvas ref={canvasRef} /> }

Our <canvas> is going to need some styles, too. For starters, we can make it so the canvas takes up the full viewport size and sits behind the content:

canvas {   position: fixed;   inset: 0;   background: #262626;   z-index: -1;   height: 100vh;   width: 100vw; }

Cool! But not much to see yet.

We need stars in our sky

We’re going to “cheat” a little here. We aren’t going to draw the “classic” pointy star shape. We’re going to use circles of differing opacities and sizes.

Draw a circle on a <canvas> is a case of grabbing a context from the <canvas> and using the arc function. Let’s render a circle, err star, in the middle. We can do this within a React useEffect:

const Starscape = () => {   const canvasRef = React.useRef(null)   const contextRef = React.useRef(null)   React.useEffect(() => {     canvasRef.current.width = window.innerWidth     canvasRef.current.height = window.innerHeight     contextRef.current = canvasRef.current.getContext('2d')     contextRef.current.fillStyle = 'yellow'     contextRef.current.beginPath()     contextRef.current.arc(       window.innerWidth / 2, // X       window.innerHeight / 2, // Y       100, // Radius       0, // Start Angle (Radians)       Math.PI * 2 // End Angle (Radians)     )     contextRef.current.fill()   }, [])   return <canvas ref={canvasRef} /> }

So what we have is a big yellow circle:

This is a good start! The rest of our code will take place within this useEffect function. That’s why the React part is kinda optional. You can extract this code out and use it in whichever form you like.

We need to think about how we’re going to generate a bunch of “stars” and render them. Let’s create a LOAD function. This function is going to handle generating our stars as well as the general <canvas> setup. We can also move the sizing logic of the <canvas> sizing logic into this function:

const LOAD = () => {   const VMIN = Math.min(window.innerHeight, window.innerWidth)   const STAR_COUNT = Math.floor(VMIN * densityRatio)   canvasRef.current.width = window.innerWidth   canvasRef.current.height = window.innerHeight   starsRef.current = new Array(STAR_COUNT).fill().map(() => ({     x: gsap.utils.random(0, window.innerWidth, 1),     y: gsap.utils.random(0, window.innerHeight, 1),     size: gsap.utils.random(1, sizeLimit, 1),     scale: 1,     alpha: gsap.utils.random(0.1, defaultAlpha, 0.1),   })) }

Our stars are now an array of objects. And each star has properties that define their characteristics, including:

  • x: The star’s position on the x-axis
  • y: The star’s position on the y-axis
  • size: The star’s size, in pixels
  • scale: The star’s scale, which will come into play when we interact with the component
  • alpha: The star’s alpha value, or opacity, which will also come into play during interactions

We can use GreenSock’s random() method to generate some of these values. You may also be wondering where sizeLimit, defaultAlpha, and densityRatio came from. These are now props we can pass to the Starscape component. We’ve provided some default values for them:

const Starscape = ({ densityRatio = 0.5, sizeLimit = 5, defaultAlpha = 0.5 }) => {

A randomly generated star Object might look like this:

{   "x": 1252,   "y": 29,   "size": 4,   "scale": 1,   "alpha": 0.5 } 

But, we need to see these stars and we do that by rendering them. Let’s create a RENDER function. This function will loop over our stars and render each of them onto the <canvas> using the arc function:

const RENDER = () => {   contextRef.current.clearRect(     0,     0,     canvasRef.current.width,     canvasRef.current.height   )   starsRef.current.forEach(star => {     contextRef.current.fillStyle = `hsla(0, 100%, 100%, $  {star.alpha})`     contextRef.current.beginPath()     contextRef.current.arc(star.x, star.y, star.size / 2, 0, Math.PI * 2)     contextRef.current.fill()   }) }

Now, we don’t need that clearRect function for our current implementation as we are only rendering once onto a blank <canvas>. But clearing the <canvas> before rendering anything isn’t a bad habit to get into, And it’s one we’ll need as we make our canvas interactive.

Consider this demo that shows the effect of not clearing between frames.

Our Starscape component is starting to take shape.

See the code
const Starscape = ({ densityRatio = 0.5, sizeLimit = 5, defaultAlpha = 0.5 }) => {   const canvasRef = React.useRef(null)   const contextRef = React.useRef(null)   const starsRef = React.useRef(null)   React.useEffect(() => {     contextRef.current = canvasRef.current.getContext('2d')     const LOAD = () => {       const VMIN = Math.min(window.innerHeight, window.innerWidth)       const STAR_COUNT = Math.floor(VMIN * densityRatio)       canvasRef.current.width = window.innerWidth       canvasRef.current.height = window.innerHeight       starsRef.current = new Array(STAR_COUNT).fill().map(() => ({         x: gsap.utils.random(0, window.innerWidth, 1),         y: gsap.utils.random(0, window.innerHeight, 1),         size: gsap.utils.random(1, sizeLimit, 1),         scale: 1,         alpha: gsap.utils.random(0.1, defaultAlpha, 0.1),       }))     }     const RENDER = () => {       contextRef.current.clearRect(         0,         0,         canvasRef.current.width,         canvasRef.current.height       )       starsRef.current.forEach(star => {         contextRef.current.fillStyle = `hsla(0, 100%, 100%, $  {star.alpha})`         contextRef.current.beginPath()         contextRef.current.arc(star.x, star.y, star.size / 2, 0, Math.PI * 2)         contextRef.current.fill()       })     }     LOAD()     RENDER()   }, [])   return <canvas ref={canvasRef} /> }

Have a play around with the props in this demo to see how they affect the the way stars are rendered.

Before we go further, you may have noticed a quirk in the demo where resizing the viewport distorts the <canvas>. As a quick win, we can rerun our LOAD and RENDER functions on resize. In most cases, we’ll want to debounce this, too. We can add the following code into our useEffect call. Note how we also remove the event listener in the teardown.

// Naming things is hard... const RUN = () => {   LOAD()   RENDER() }  RUN()  // Set up event handling window.addEventListener('resize', RUN) return () => {   window.removeEventListener('resize', RUN) }

Cool. Now when we resize the viewport, we get a new generated starry.

Interacting with the starry backdrop

Now for the fun part! Let’s make this thing interactive.

The idea is that as we move our pointer around the screen, we detect the proximity of the stars to the mouse cursor. Depending on that proximity, the stars both brighten and scale up.

We’re going to need to add another event listener to pull this off. Let’s call this UPDATE. This will work out the distance between the pointer and each star, then tween each star’s scale and alpha values. To make sure those tweeted values are correct, we can use GreenSock’s mapRange() utility. In fact, inside our LOAD function, we can create references to some mapping functions as well as a size unit then share these between the functions if we need to.

Here’s our new LOAD function. Note the new props for scaleLimit and proximityRatio. They are used to limit the range of how big or small a star can get, plus the proximity at which to base that on.

const Starscape = ({   densityRatio = 0.5,   sizeLimit = 5,   defaultAlpha = 0.5,   scaleLimit = 2,   proximityRatio = 0.1 }) => {   const canvasRef = React.useRef(null)   const contextRef = React.useRef(null)   const starsRef = React.useRef(null)   const vminRef = React.useRef(null)   const scaleMapperRef = React.useRef(null)   const alphaMapperRef = React.useRef(null)      React.useEffect(() => {     contextRef.current = canvasRef.current.getContext('2d')     const LOAD = () => {       vminRef.current = Math.min(window.innerHeight, window.innerWidth)       const STAR_COUNT = Math.floor(vminRef.current * densityRatio)       scaleMapperRef.current = gsap.utils.mapRange(         0,         vminRef.current * proximityRatio,         scaleLimit,         1       );       alphaMapperRef.current = gsap.utils.mapRange(         0,         vminRef.current * proximityRatio,         1,         defaultAlpha       );     canvasRef.current.width = window.innerWidth     canvasRef.current.height = window.innerHeight     starsRef.current = new Array(STAR_COUNT).fill().map(() => ({       x: gsap.utils.random(0, window.innerWidth, 1),       y: gsap.utils.random(0, window.innerHeight, 1),       size: gsap.utils.random(1, sizeLimit, 1),       scale: 1,       alpha: gsap.utils.random(0.1, defaultAlpha, 0.1),     }))   } }

And here’s our UPDATE function. It calculates the distance and generates an appropriate scale and alpha for a star:

const UPDATE = ({ x, y }) => {   starsRef.current.forEach(STAR => {     const DISTANCE = Math.sqrt(Math.pow(STAR.x - x, 2) + Math.pow(STAR.y - y, 2));     gsap.to(STAR, {       scale: scaleMapperRef.current(         Math.min(DISTANCE, vminRef.current * proximityRatio)       ),       alpha: alphaMapperRef.current(         Math.min(DISTANCE, vminRef.current * proximityRatio)       )     });   }) }; 

But wait… it doesn’t do anything?

Well, it does. But, we haven’t set our component up to show updates. We need to render new frames as we interact. We can reach for requestAnimationFrame often. But, because we’re using GreenSock, we can make use of gsap.ticker. This is often referred to as “the heartbeat of the GSAP engine” and it’s is a good substitute for requestAnimationFrame.

To use it, we add the RENDER function to the ticker and make sure we remove it in the teardown. One of the neat things about using the ticker is that we can dictate the number of frames per second (fps). I like to go with a “cinematic” 24fps:

// Remove RUN LOAD() gsap.ticker.add(RENDER) gsap.ticker.fps(24)  window.addEventListener('resize', LOAD) document.addEventListener('pointermove', UPDATE) return () => {   window.removeEventListener('resize', LOAD)   document.removeEventListener('pointermove', UPDATE)   gsap.ticker.remove(RENDER) }

Note how we’re now also running LOAD on resize. We also need to make sure our scale is being picked up in that RENDER function when using arc:

const RENDER = () => {   contextRef.current.clearRect(     0,     0,     canvasRef.current.width,     canvasRef.current.height   )   starsRef.current.forEach(star => {     contextRef.current.fillStyle = `hsla(0, 100%, 100%, $  {star.alpha})`     contextRef.current.beginPath()     contextRef.current.arc(       star.x,       star.y,       (star.size / 2) * star.scale,       0,       Math.PI * 2     )     contextRef.current.fill()   }) }

It works! 🙌

It’s a very subtle effect. But, that’s intentional because, while it’s is super neat, we don’t want this sort of thing to distract from the actual content. I’d recommend playing with the props for the component to see different effects. It makes sense to set all the stars to low alpha by default too.

The following demo allows you to play with the different props. I’ve gone for some pretty standout defaults here for the sake of demonstration! But remember, this article is more about showing you the techniques so you can go off and make your own cool backdrops — while being mindful of how it interacts with content.


There is one issue with our interactive starry backdrop. If the mouse cursor leaves the <canvas>, the stars stay bright and upscaled but we want them to return to their original state. To fix this, we can add an extra handler for pointerleave. When the pointer leaves, this tweens all of the stars down to scale 1 and the original alpha value set by defaultAlpha.

const EXIT = () => {   gsap.to(starsRef.current, {     scale: 1,     alpha: defaultAlpha,   }) }  // Set up event handling window.addEventListener('resize', LOAD) document.addEventListener('pointermove', UPDATE) document.addEventListener('pointerleave', EXIT) return () => {   window.removeEventListener('resize', LOAD)   document.removeEventListener('pointermove', UPDATE)   document.removeEventListener('pointerleave', EXIT)   gsap.ticker.remove(RENDER) }

Neat! Now our stars scale back down and return to their previous alpha when the mouse cursor leaves the scene.

Bonus: Adding an Easter egg

Before we wrap up, let’s add a little Easter egg surprise to our interactive starry backdrop. Ever heard of the Konami Code? It’s a famous cheat code and a cool way to add an Easter egg to our component.

We can practically do anything with the backdrop once the code runs. Like, we could make all the stars pulse in a random way for example. Or they could come to life with additional colors? It’s an opportunity to get creative with things!

We’re going listen for keyboard events and detect whether the code gets entered. Let’s start by creating a variable for the code:

const KONAMI_CODE =   'arrowup,arrowup,arrowdown,arrowdown,arrowleft,arrowright,arrowleft,arrowright,keyb,keya';

Then we create a second effect within our the starry backdrop. This is a good way to maintain a separation of concerns in that one effect handles all the rendering, and the other handles the Easter egg. Specifically, we’re listening for keyup events and check whether our input matches the code.

const codeRef = React.useRef([]) React.useEffect(() => {   const handleCode = e => {     codeRef.current = [...codeRef.current, e.code]       .slice(         codeRef.current.length > 9 ? codeRef.current.length - 9 : 0       )     if (codeRef.current.join(',').toLowerCase() === KONAMI_CODE) {       // Party in here!!!     }   }   window.addEventListener('keyup', handleCode)   return () => {     window.removeEventListener('keyup', handleCode)   } }, [])

We store the user input in an Array that we store inside a ref. Once we hit the party code, we can clear the Array and do whatever we want. For example, we may create a gsap.timeline that does something to our stars for a given amount of time. If this is the case, we don’t want to allow Konami code to input while the timeline is active. Instead, we can store the timeline in a ref and make another check before running the party code.

const partyRef = React.useRef(null) const isPartying = () =>   partyRef.current &&   partyRef.current.progress() !== 0 &&   partyRef.current.progress() !== 1;

For this example, I’ve created a little timeline that colors each star and moves it to a new position. This requires updating our LOAD and RENDER functions.

First, we need each star to now have its own hue, saturation and lightness:

// Generating stars! ⭐️ starsRef.current = new Array(STAR_COUNT).fill().map(() => ({   hue: 0,   saturation: 0,   lightness: 100,   x: gsap.utils.random(0, window.innerWidth, 1),   y: gsap.utils.random(0, window.innerHeight, 1),   size: gsap.utils.random(1, sizeLimit, 1),   scale: 1,   alpha: defaultAlpha }));

Second, we need to take those new values into consideration when rendering takes place:

starsRef.current.forEach((star) => {   contextRef.current.fillStyle = `hsla(     $  {star.hue},     $  {star.saturation}%,     $  {star.lightness}%,     $  {star.alpha}   )`;   contextRef.current.beginPath();   contextRef.current.arc(     star.x,     star.y,     (star.size / 2) * star.scale,     0,     Math.PI * 2   );   contextRef.current.fill(); });

And here’s the fun bit of code that moves all the stars around:

partyRef.current = gsap.timeline().to(starsRef.current, {   scale: 1,   alpha: defaultAlpha });  const STAGGER = 0.01;  for (let s = 0; s < starsRef.current.length; s++) {   partyRef.current     .to(     starsRef.current[s],     {       onStart: () => {         gsap.set(starsRef.current[s], {           hue: gsap.utils.random(0, 360),           saturation: 80,           lightness: 60,           alpha: 1,         })       },       onComplete: () => {         gsap.set(starsRef.current[s], {           saturation: 0,           lightness: 100,           alpha: defaultAlpha,         })       },       x: gsap.utils.random(0, window.innerWidth),       y: gsap.utils.random(0, window.innerHeight),       duration: 0.3     },     s * STAGGER   ); }

From there, we generate a new timeline and tween the values of each star. These new values get picked up by RENDER. We’re adding a stagger by positioning each tween in the timeline using GSAP’s position parameter.

That’s it!

That’s one way to make an interactive starry backdrop for your site. We combined GSAP and an HTML <canvas>, and even sprinkled in some React that makes it more configurable and reusable. We even dropped an Easter egg in there!

Where can you take this component from here? How might you use it on a site? The combination of GreenSock and <canvas> is a lot of fun and I’m looking forward to seeing what you make! Here are a couple more ideas to get your creative juices flowing…

An Interactive Starry Backdrop for Content originally published on CSS-Tricks. You should get the newsletter.


, , ,

Generate a Pull Request of Static Content With a Simple HTML Form

Jamstack has been in the website world for years. Static Site Generators (SSGs) — which often have content that lives right within a GitHub repo itself — are a big part of that story. That opens up the idea of having contributors that can open pull requests to add, change, or edit content. Very useful!

Examples of this are like:

Why built with a static site approach?

When we need to build content-based sites like this, it’s common to think about what database to use. Keeping content in a database is a time-honored good idea. But it’s not the only approach! SSGs can be a great alternative because…

  • They are cheap and easy to deploy. SSGs are usually free, making them great for an MVP or a proof of concept.
  • They have great security. There is nothing to hack through the browser, as all the site contains is often just static files.
  • You’re ready to scale. The host you’re already on can handle it.

There is another advantage for us when it comes to a content site. The content of the site itself can be written in static files right in the repo. That means that adding and updating content can happen right from pull requests on GitHub, for example. Even for the non-technically inclined, it opens the door to things like Netlify CMS and the concept of open authoring, allowing for community contributions.

But let’s go the super lo-fi route and embrace the idea of pull requests for content, using nothing more than basic HTML.

The challenge

How people contribute adding or updating a resource isn’t always perfectly straightforward. People need to understand how to fork your repository, how to and where to add their content, content formatting standards, required fields, and all sorts of stuff. They might even need to “spin up” the site themselves locally to ensure the content looks right.

People who seriously want to help our site sometimes will back off because the process of contributing is a technological hurdle and learning curve — which is sad.

You know what anybody can do? Use a <form>

Just like a normal website, the easy way for people to submit a content is to fill out a form and submit it with the content they want.

What if we can make a way for users to contribute content to our sites by way of nothing more than an HTML <form> designed to take exactly the content we need? But instead of the form posting to a database, it goes the route of a pull request against our static site generator? There is a trick!

The trick: Create a GitHub pull request with query parameters

Here’s a little known trick: We can pre-fill a pull request against our repository by adding query parameter to a special GitHub URL. This comes right from the GitHub docs themselves.

Let’s reverse engineer this.

If we know we can pre-fill a link, then we need to generate the link. We’re trying to make this easy remember. To generate this dynamic data-filled link, we’ll use a touch of JavaScript.

So now, how do we generate this link after the user submits the form?

Demo time!

Let’s take the Serverless site from CSS-Tricks as an example. Currently, the only way to add a new resource is by forking the repo on GitHub and adding a new Markdown file. But let’s see how we can do it with a form instead of jumping through those hoops.

The Serverless site itself has many categories (e.g. for forms) we can contribute to. For the sake of simplicity, let’s focus on the “Resources” category. People can add articles about things related to Serverless or Jamstack from there.

The Resources page of the CSS-Tricks Serverless site. The site is primarily purple in varying shades with accents of orange. The page shows a couple of serverless resources in the main area and a list of categories in the right sidebar.

All of the resource files are in this folder in the repo.

Showing the main page of the CSS-Tricks Serverless repo in GitHub, displaying all the files.

Just picking a random file from there to explore the structure…

--- title: "How to deploy a custom domain with the Amplify Console" url: "https://read.acloud.guru/how-to-deploy-a-custom-domain-with-the-amplify-console-a884b6a3c0fc" author: "Nader Dabit" tags: ["hosting", "amplify"] ---  In this tutorial, we’ll learn how to add a custom domain to an Amplify Console deployment in just a couple of minutes.

Looking over that content, our form must have these columns:

  • Title
  • URL
  • Author
  • Tags
  • Snippet or description of the link.

So let’s build an HTML form for all those fields:

<div class="columns container my-2">   <div class="column is-half is-offset-one-quarter">   <h1 class="title">Contribute to Serverless Resources</h1>    <div class="field">     <label class="label" for="title">Title</label>     <div class="control">       <input id="title" name="title" class="input" type="text">     </div>   </div>      <div class="field">     <label class="label" for="url">URL</label>     <div class="control">       <input id="url" name="url" class="input" type="url">     </div>   </div>        <div class="field">     <label class="label" for="author">Author</label>     <div class="control">       <input id="author" class="input" type="text" name="author">     </div>   </div>      <div class="field">     <label class="label" for="tags">Tags (comma separated)</label>     <div class="control">       <input id="tags" class="input" type="text" name="tags">     </div>   </div>        <div class="field">     <label class="label" for="description">Description</label>     <div class="control">       <textarea id="description" class="textarea" name="description"></textarea>     </div>   </div>       <!-- Prepare the JavaScript function for later -->   <div class="control">     <button onclick="validateSubmission();" class="button is-link is-fullwidth">Submit</button>   </div>        </div> </div>

I’m using Bulma for styling, so the class names in use here are from that.

Now we write a JavaScript function that transforms a user’s input into a friendly URL that we can combine as GitHub query parameters on our pull request. Here is the step by step:

  • Get the user’s input about the content they want to add
  • Generate a string from all that content
  • Encode the string to format it in a way that humans can read
  • Attach the encoded string to a complete URL pointing to GitHub’s page for new pull requests

Here is the Pen:

After pressing the Submit button, the user is taken right to GitHub with an open pull request for this new file in the right location.

GitHub pull request screen showing a new file with content.

Quick caveat: Users still need a GitHub account to contribute. But this is still much easier than having to know how to fork a repo and create a pull request from that fork.

Other benefits of this approach

Well, for one, this is a form that lives on our site. We can style it however we want. That sort of control is always nice to have.

Secondly, since we’ve already written the JavaScript, we can use the same basic idea to talk with other services or APIs in order to process the input first. For example, if we need information from a website (like the title, meta description, or favicon) we can fetch this information just by providing the URL.

Taking things further

Let’s have a play with that second point above. We could simply pre-fill our form by fetching information from the URL provided for the user rather than having them have to enter it by hand.

With that in mind, let’s now only ask the user for two inputs (rather than four) — just the URL and tags.

How does this work? We can fetch meta information from a website with JavaScript just by having the URL. There are many APIs that fetch information from a website, but you might the one that I built for this project. Try hitting any URL like this:


The demo above uses that as an API to pre-fill data based on the URL the user provides. Easier for the user!

Wrapping up

You could think of this as a very minimal CMS for any kind of Static Site Generator. All you need to do is customize the form and update the pre-filled query parameters to match the data formats you need.

How will you use this sort of thing? The four sites we saw at the very beginning are good examples. But there are so many other times where might need to do something with a user submission, and this might be a low-lift way to do it.

The post Generate a Pull Request of Static Content With a Simple HTML Form appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.


, , , , , , ,

A Deep Dive on Skipping to Content

While most people browsing the web on a computer use a mouse, many rely on their keyboard instead. Theoretically, using a web page with the keyboard should not be a problem — press the TAB key to move the keyboard focus from one focusable element to the next then press ENTER to activate, easy! However, many (if not most) websites tend to have a menu of links at the top of the page, and it can sometimes take a lot of presses to get to the content that you want. Take the homepage of 20min for example, one of the biggest news sites here in Switzerland. You want to read the top story? That’ll be the best part of 40 key presses — not the best best use of anyone’s time.

So, if you want keyboard users to actually use your website rather than getting bored and going somewhere else, you need to do a bit of work behind the scenes to make skipping straight to your main content quicker and easier. You can find all sorts of techniques for this scattered across the web (including here at CSS-Tricks) but most are missing a trick or two and many recommend using outdated or deprecated code. So, in this article, I’m going to take a deep dive into skipping to content and cover everything in a 2021-friendly fashion.

Two types of keyboard users

Although there are numerous differences in the exact type of keyboard or equivalent switch device that people use to navigate, from a coding point of view, we only need to consider two groups:

  • People who use the keyboard in conjunction with a screen reader — like NVDA or JAWS on a PC, or VoiceOver on a Mac — that reads the content of the screen out loud. These devices are often used by people with more severe visual impairments.
  • All other keyboard users.

Our skip-to-content techniques need to cater to both these groups while not getting in the way of all the mouse users. We will use two complementary techniques to get the best result: landmarks and skip links.

Look at a basic example

I created an example website I’m calling Style Magic to illustrate the techniques we’re covering. We’ll start off with it in a state that works fine for a mouse user but is a bit of a pain for those using a keyboard. You can find the base site and the versions for each of the techniques in this collection over at CodePen and, because testing keyboard navigation is a little tricky on CodePen, you can also find standalone versions here.

Try using the TAB key to navigate this example. (It’s easier on the standalone page; TAB to move from one link to the next, and SHIFT+TAB to go backwards.) You will find that it’s not too bad, but only because there aren’t many menu items.

If you have the time and are on Windows then as I’d also encourage you to download a free copy of the NVDA screen reader and try all the examples with that too, referring to WebAIM’s overview for usage. Most of you on a Mac already have the VoiceOver screen reader available and WebAIM has a great intro to using it as well.

Adding landmarks

One of the things that screen reading software can do is display a list of landmarks that they find on a web page. Landmarks represent significant areas of a page, and the user can pull up that list and then jump straight to one of those landmarks.

If you are using NVDA with a full keyboard, you hit INS+F7 to bring up the “Elements List” then ALT+d to show the landmarks. (You can find a similar list on VoiceOver by using the Web Item Rotor.) If you do that on example site, though, you will only be presented with an unhelpful empty list.

An open dialog with a UI for viewing different types of content on the page in a screen reader, including links, headings, form fields, buttons, and landmarks. Landmarks is selected and there are no results in the window.
A disappointingly empty list of landmarks in NVDA

Let’s fix that first.

Adding landmarks is incredibly easy and, if you are using HTML5, you might already have them on your website without realizing it, as they are directly linked to the HTML5 semantic elements (<header>, <main>, <footer>, and so on).

Here’s a before and after of the HTML used to generate the header section of the site:

<div class="bg-dark">   <div class="content-width flex-container">     <div class="branding"><a href="#">Style Magic</a></div>     <div class="menu-right with-branding">       <a href="#">Home</a>       <!-- etc. -->       </div>   </div> </div>


<div class="bg-dark">  <header class="content-width flex-container">         <section class="branding"><a href="#">Style Magic</a></section>     <nav aria-label="Main menu" class="menu-right with-branding">       <a href="#">Home</a>       <!-- etc. -->     </nav>   </header> </div>

The classes used remain the same, so we don’t need to make any changes at all to the CSS.

Here’s a full list of the changes we need to make in our example site:

  • The <div> denoting the header at the top of the page is now a <header> element.
  • The <div> containing the branding is now a <section> element.
  • The two <div>s containing menus have been replaced with <nav> elements.
  • The two new <nav> elements have been given an aria-label attribute which describes them: “Main menu” for the menu at the top of the page, and “Utility menu” for the menu at the bottom of the page.
  • The <div> containing the main content of the page is now a <main> element.
  • The <div> denoting the footer at the bottom of the page is now a <footer> element.

You can see the full updated HTML on CodePen.

Let’s try that landmark list trick in NVDA again (INS+F7 then ALT+d — here’s the link to the standalone page so you can test yourself):

Open screen reader dialog window showing the landmarks on the current page, including "banner," "main," and "content info."
Hurrah for landmarks!

Great! We now have the banner landmark (mapped to the <header> element), Main menu; navigation (mapped to the top <nav> element, and displaying our aria-label), main (mapped to <main>) and content info (mapped to footer). From this dialog I can use TAB and the cursor keys to select the main landmark and skip straight to the content of the page, or even better, I can just press the D key when browsing the page to jump from one landmark role directly to the next. Users of the JAWS screen reader have it even easier — they can simply press Q when browsing to jump straight to the main landmark.

As an added bonus, using semantic elements also help search engines understand and index your content better. That’s a nice little side benefit of making a site much more accessible.

I expect you’re sitting back thinking “job done” at this point. Well, I’m afraid there’s always a “but” to consider. Google did some research way back in 2011 on the use of CTRL+f to search within a web page and found that a startling 90% of people either didn’t know it existed, or have never used it. Users with a screen reader behave in much the same way when it comes to landmarks — a large portion of them simply do not use this feature even though it’s very useful. So, we’re going to add a skip link to our site to help out both groups as well as all those keyboard users who don’t use a screen reader.

The basic requirements for what makes a good skip link are:

  • It should be perceivable to all keyboard users (including screen reader users) when it is needed.
  • It should provide enough information to the keyboard user to explain what it does.
  • It should work on as wide a range of current browsers as possible.
  • It should not interfere with the browsing of a mouse user.

Step 1: Improving the keyboard focus appearance

First up, we’re going to improve the visibility of the keyboard focus across the site. You can think of the keyboard focus as the equivalent to the position of the cursor when you are editing text in a word processor. When you use the TAB key to navigate the keyboard focus moves from link to link (or form control).

The latest web browsers do a reasonable job of showing the position of the keyboard focus but can still benefit from a helping hand. There are lots of creative ways to style the focus ring, though our goal is making it stand out more than anything.

We can use the :focus pseudo-class for our styling (and it’s a good idea to apply the same styles to :hover as well, which we’ve already done on the example site — CodePen, live site). That’s the very least we can do, though it’s common to go further and invert the link colors on :focus throughout the page.

Here’s some CSS for our :focus state (a copy of what we already have for :hover):

a:focus { /* generic rule for entire page */   border-bottom-color: #1295e6; } .menu-right a:focus, .branding a:focus {   /* inverted colors for links in the header and footer */   background-color: white;   color: #1295e6; }

Step 2: Adding the HTML and CSS

The last change is to add the skip link itself to the HTML and CSS. It consists of two parts, the trigger (the link) and the target (the landmark). Here’s the HTML that I recommend for the trigger, placed right at the start of the page just inside the <header> element:

<header class="content-width flex-container">   <a href="#skip-link-target" class="text-assistive display-at-top-on-focus">Skip to main content.</a>   <!-- etc. --> </header>

And here’s the HTML for the target, placed directly before the start of the <main> content:

<a href="#skip-link-target" class="text-assistive display-at-top-on-focus" id="skip-link-target">Start of main content.</a>  <main class="content-width">   <!-- etc. --> </main>

Here’s how the HTML works:

  • The skip link trigger links to the skip link target using a standard page fragment (href="#skip-link-target") which references the id attribute of the target (id="skip-link-target"). Following the link moves the keyboard focus from the trigger to the target.
  • We link to an anchor (<a>) element rather than adding the id attribute directly to the <main> element for two reasons. First, it avoids any issues with the keyboard focus not moving correctly (which can be a problem in some browsers); secondly, it means we can provide clear feedback to the user to show that the skip link worked.
  • The text of the two links is descriptive so as to clearly explain to the user what is happening.

We now have a functioning skip link, but there’s one problem: it’s visible to everyone. We’ll use CSS to hide it from view by default, which keeps it out of the way of mouse users, then have it appear only when it receives the keyboard focus. There are lots of ways to do this and most of them are okay, but there’s a couple of wrong ways that you should avoid:

  • Do: use clip-path to make the link invisible, or use transform: translate or position: absolute to position it off screen instead.
  • Don’t: use display: none, visibility: hidden, the hidden attribute, or set the width or height of the skip link to zero. All of these will make your skip link unusable for one or both classes of keyboard users.
  • Don’t: use clip as it is deprecated.

Here’s the code that I recommend to hide both links. Using clip-path along with its prefixed -webkit- version hits the spot for 96.84% of users at time of writing, which (in my opinion) is fine for most websites and use cases. Should your use case require it, there are a number of other techniques available that are detailed on WebAIM.

.text-assistive {   -webkit-clip-path: polygon(0 0, 0 0, 0 0, 0 0);   clip-path: polygon(0 0, 0 0, 0 0, 0 0);   box-sizing: border-box;   position: absolute;   margin: 0;   padding: 0; }

And to show the links when they have focus, I recommend using a version of this CSS, with colors and sizing to match your branding:

.text-assistive.display-at-top-on-focus {   top: 0;   left: 0;   width: 100%;   } .text-assistive.display-at-top-on-focus:focus {   -webkit-clip-path: none;   clip-path: none;   z-index: 999;   height: 80px;   line-height: 80px;   background: white;   font-size: 1.2rem;   text-decoration: none;   color: #1295e6;   text-align: center; } #skip-link-target:focus {   background: #084367;   color: white; }

This provides for a very visible display of the trigger and the target right at the top of the page where a user would expect to see the keyboard focus directly after loading the page. There is also a color change when you follow the link to provide clear feedback that something has happened. You can fiddle with the code yourself on CodePen (shown below) and test it with NVDA on the standalone page.

Taking this further

Skip links aren’t just for Christmas your main menu, they are useful whenever your web page has a long list of links. In fact, this CodePen demonstrates a good approach to skip links within the content of your pages (standalone page here) using transform: translateY() in CSS to hide and show the triggers and targets. And if you are in the “lucky” position of needing to support older browsers, then you here’s a technique for that on this CodePen (standalone page here).

Let’s check it out!

To finish off, here are a couple of short videos demonstrating how the skip links work for our two classes of keyboard user.

Here’s how the finished skip link works when using the NVDA screen reader:

Here it is again when browsing using the keyboard without a screen reader:

We just looked at what I consider to be a modern approach for accessible skip links in 2021. We took some of the ideas from past examples while updating them to account for better CSS practices, an improved UI for keyboard users, and an improved experience for those using a screen reader, thanks to an updated approach to the HTML.

The post A Deep Dive on Skipping to Content appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.


, , ,

Positioning Overlay Content with CSS Grid

Not news to any web developer in 2021: CSS Grid is an incredibly powerful tool for creating complex, distinct two-dimensional modern web layouts.

Recently, I have been experimenting with CSS Grid and alignment properties to create component layouts that contain multiple overlapping elements. These layouts could be styled using absolute positioning and a mix of offset values (top, right, bottom, left), negative margins, and transforms. But, with CSS Grid, positioning overlay elements can be built using more logical, readable properties and values. The following are a few examples of where these grid properties come in handy.

It will help to read up on grid-template-areas and grid-area properties if you’re not yet familiar with them.

Expanding images inside limited dimensions

In the demo, there is a checkbox that toggles the overflow visibility so that we can see where the image dimensions expand beyond the container on larger viewport widths.

Here’s a common hero section with a headline overlapping an image. Although the image is capped with a max-width, it scales up to be quite tall on desktop. Because of this, the content strategy team has requested that some of the pertinent page content below the hero remain visible in the viewport as much as possible. Combining this layout technique and a fluid container max-height using the CSS clamp() function, we can develop something that adjusts based on the available viewport space while anchoring the hero image to the center of the container.

CSS clamp(), along with the min() and max() comparison functions, are well-supported in all modern browsers. Haven’t used them? Ahmad Shadeed conducts a fantastic deep dive in this article.

Open this Pen and resize the viewport width. Based on the image dimensions, the container height expands until it hits a maximum height. Notice that the image continues to grow while remaining centered in the container. Resize the viewport height and the container will flex between its max-height’s lower and upper bound values defined in the clamp() function.

Prior to using grid for the layout styles, I might have tried absolute positioning on the image and title, used an aspect ratio padding trick to create a responsive height, and object-fit to retain the ratio of the image. Something like this could get it there:

.container {   position: relative;   max-height: clamp(400px, 50vh, 600px); }  .container::before {   content: '';   display: block;   padding-top: 52.25%; }  .container > * {   max-width: 1000px; }  .container .image {   position: absolute;   top: 0;   left: 50%;   transform: translateX(-50%);   width: 100%;   height: 100%;   object-fit: cover; }  .container .title {   position: absolute;   top: 50%;   left: 50%;   transform: translate(-50%, -50%);   width: 100%;   text-align: center; }

Maybe it’s possible to whittle the code down some more, but there’s still a good chunk of styling needed. Managing the same responsive layout with CSS Grid will simplify these layout style rules while making the code more readable. Check it out in the following iteration:

.container {   display: grid;   grid-template: "container";   place-items: center;   place-content: center;   overflow: hidden;   max-height: clamp(450px, 50vh, 600px); }  .container > * {   grid-area: container;   max-width: 1000px; }

place-content: center instructs the image to continue growing out from the middle of the container. Remove this line and see that, while the image is still vertically centered via place-items, once the max-height is reached, the image will stick to the top of the container block and go on scaling beyond its bottom. Set place-content: end center and you’ll see the image spill over the top of the container.

This behavior may seem conceptually similar to applying object-fit: cover on an image as a styling method for preserving its intrinsic ratio while resizing to fill its content-box dimensions (it was utilized in the absolute position iteration). However, in this grid context, the image element governs the height of its parent and, once the parent’s max-height is reached, the image continues to expand, maintaining its ratio, and remains completely visible if the parent overflow is shown. object-fit could even be used with the aspect-ratio property here to create a consistent aspect ratio pattern for the hero image:

.container .image {   width: 100%;   height: auto;   object-fit: cover;   aspect-ratio: 16 / 9; }

The overlay grid-area

Moving on to the container’s direct children, grid-area arranges each of them so that they overlap the same space. In this example, grid-template-areas with the named grid area makes the code a little more readable and works well as a pattern for other overlay-style layouts within a component library. That being said, it is possible to get this same result by removing the template rule and, instead of grid-area: container, using integers:

.container > * {   grid-area: 1 / 1; }

This is shorthand for grid-row-start, grid-column-start, grid-row-end, and grid-column-end. Since the siblings in this demo all share the same single row/column area, only the start lines need to be set for the desired result.

Setting place-self to place itself

Another common overlay pattern can be seen on image carousels. Interactive elements are often placed on top of the carousel viewport. I’ve extended the first demo and replaced the static hero image with a carousel.

Same story as before: This layout could fall back on absolute positioning and use integer values in a handful of properties to push and pull elements around their parent container. Instead, we’ll reuse the grid layout rulesets from the previous demo. Once applied, it appears as you might expect: all of the child elements are centered inside the container, overlapping one another.

Picture of a cute light brown puppy with the words Welcome to the Snuggle Zone on top in white. The text overlaps over text elements and is hard to read.
With place-items: center declared on the container, all of its direct children will overlap one another.

The next step is to set alignment values on individual elements. The place-self property—shorthand for align-self and justify-self—provides granular control over the position of a single item inside the container. Here are the layout styles altogether:

.container {   display: grid;   grid-template:"container";   place-items: center;   place-content: center;   overflow: hidden;   max-height: clamp(450px, 50vh, 600px); }  .container > * {   grid-area: container;   max-width: 1000px; }  .title {   place-self: start center; }  .carousel-control.prev {   place-self: center left; }  .carousel-control.next {   place-self: center right; }  .carousel-dots {   place-self: end center; }

There’s just one small problem: The title and carousel dot indicators get pulled out into the overflow when the image exceeds the container dimensions.

To properly contain these elements within the parent, a grid-template-row value needs to be 100% of the container, set here as one fractional unit.

.container {   grid-template-areas: "container";   grid-template-rows: 1fr; }

For this demo, I leaned into the the grid-template shorthand (which we will see again later in this article).

.container {   grid-template: "container" 1fr; }

After providing that little update, the overlay elements stay within the parent container, even when the carousel images spread beyond the carousel’s borders.

Alignment and named grid-template-areas

Let’s use the previous overlay layout methods for one more example. In this demo, each box contains elements positioned in different areas on top of an image.

For the first iteration, a named template area is declared to overlay the children on the parent element space, similar to the previous demos:

.box {   display: grid;   grid-template-areas: "box"; }  .box > *, .box::before {   grid-area: box; }

The image and semi-transparent overlay now cover the box area, but these style rules also stretch the other items over the entire space. This seems like the right time for place-self to pepper these elements with some alignment magic!

.tag {   place-self: start; }  .title {   place-self: center; }  .tagline {   place-self: end start; }  .actions {   place-self: end; }

That‘s looking great! Every element is positioned in their defined places over the image as intended. Well, almost. There’s a bit of nuance to the bottom area where the tagline and action buttons reside. Hover over an image to reveal the tagline. This might look fine with a short string of text on a desktop screen, but if the tagline becomes longer (or the boxes in the viewport smaller), it will eventually extend behind the action buttons.

A two by two grid of images with text overlaid on top, as well as a tag label in the top right corner and, tagline in the bottom left corner and actions to like and share in the bottom right corner of each one.
Note how the tagline in the first box on the second row overlaps the action buttons.

To clean this up, the grid-template-areas use named areas for the tagline and actions. The grid-template-columns rule is introduced so that the actions container only scales to accommodate the size of its buttons while the tagline fills in the rest of the inline area using the 1fr value.

.box {   display: grid;   grid-template-areas: "tagline actions";   grid-template-columns: 1fr auto; }

This can also be combined with the grid-template shorthand. The column values are defined after a slash, like so:

.box {   grid-template: "tagline actions" / 1fr auto; }

The grid-area is then converted to integers now that the “box” keyword has been removed.

.box > *, .box::before {   grid-area: 1 / 1 / -1 / -1; }

Everything should look the way it did before. Now for the finishing touch. The tagline and actions keywords are set as their respective element grid-area values:

.tagline {   grid-area: tagline;   place-self: end start; }  .actions {   grid-area: actions;   place-self: end; }

Now, when hovering over the cards in the demo, the tagline wraps to multiple lines when the text becomes too long, rather than pushing past the action buttons like it did before.

Named grid lines

Looking back at the first iteration of this code, I really liked having the default grid-area set to the box keyword. There’s a way to get that back.

I’m going add some named grid lines to the template. In the grid-template rule below, the first line defines the named template areas, which also represents the row. After the slash are the explicit column sizes (moved to a new line for readability). The [box-start] and [box-end] custom identifiers represent the box area.

.box {   display: grid;   grid-template:      [box-start] "tagline actions" [box-end] /     [box-start] 1fr auto [box-end]; }  .box > *, .box::before {   grid-area: box; }

Passing a name with the -start and -end syntax into brackets defines an area for that name. This name, known as a custom ident, can be anything but words from the CSS spec should be avoided.

Logical placement values

One of the really interesting parts to observe in this last example is the use of logical values, like start and end, for placing elements. If the direction or writing-mode were to change, then the elements would reposition accordingly.

When the “right to left” direction is selected from the dropdown, the inline start and end positions are reversed. This layout is ready to accommodate languages, such as Arabic or Hebrew, that read from right to left without having to override any of the existing CSS.

Wrapping up

I hope you enjoyed these demos and that they provide some new ideas for your own project layouts—I’ve compiled a collection of examples you can check out over at CodePen. The amount of power packed into the CSS Grid spec is incredible. Take a minute to reflect on the days of using floats and a clearfix for primitive grid row design, then return to the present day and behold the glorious layout and display properties of today‘s CSS. To make these things work well is no easy task, so let’s applaud the members of the CSS working group. The web space continues to evolve and they continue to make it a fun place to build.

Now let’s release container queries and really get this party started.

The post Positioning Overlay Content with CSS Grid appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.


, , ,

Hiding Content Responsibly

We’ve covered the idea of hiding things in CSS many times here, the most recent post being Marko Ilic’s “Comparing Various Ways to Hide Things in CSS” which did a nice job of comparing different techniques which you’d use in different situations. Hugo “Kitty” Giraudel has done something similar in “Hiding Content Responsibly” which looks at 10 methods—and even those, you could say, aren’t totally comprehensive.

Does this mean CSS is messy and incomprehensible? Nah. I feel like all the methods are logical, have good use cases, and result in good outcomes. Allow me to do a have a pretend conversation walking through my thought process here.

I need to hide this thing completely. For everyone.

No problem, that’s what the aria-hidden attribute is for.

I need to hide this thing, but only hide it for screen readers, not visually. (For example, an icon that has no additional meaning for screen readers, as there is an accessible label nearby.)

No problem, use display: none;.

I need to hide this thing, but only visually, not for screen readers. (For example, the contents of non-active tabs.)

No problem, use a .sr-only class. That leaves it accessible but hides it visually until you remove that class.

Oops, I actually want to hide this thing visually, but I still want it to take up physical space, not collapse. (For example, say a button has a loading spinner icon that is only needed when performing an action. The size of the button should factor in the size of that icon all the time, not just when the spinner is visible. That way, there’s no layout shifting when that icon comes and goes.)

No problem, use transform: scale(0) which will visually collapse it, but the original space will remain, and will leave it accessible to screen readers.

Oh nice, I could transition the transform, too, I suppose. But actually, that transition doesn’t fit my site well. I just want something I can fade out and fade in.

The opacity property is transitional, so transition that between 0 and 1 for fades. The good news is that visibility is also transitional. When fading out, use visibility: hidden, and when fading in, use visibility: visible to hide and unhide the thing from screen readers.

That’s not entirely comprehensive, but I find that covers 95% of hiding cases.

The post Hiding Content Responsibly appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, ,

Using a brightness() filter to generically highlight content

Rick Strahl:

I can’t tell you how many times over the years I’ve implemented a custom ‘button’ like CSS implementation. Over the years I’ve used images, backgrounds, gradients, and opacity to effectively ‘highlight’ a control. All that works of course, but the problem with most of these approaches is that one way or the other you’re hard coding a color value, image, or gradient.

You certainly have a lot more control if you specify exact colors, but if you can pull off brightening, darkening, or even a hue-shift in a way that feels cohesive on your site, it’s certainly a lot less code to maintain,

.button.specific-button {   background: #4CAF50; } .button.specific-button:focus, .button.specific-button:hover {   background: #A5D6A7; }  /* vs. */ .button:focus, .button:hover {   filter: brightness(120%); }  /* or maybe you're super hardcore and do it everywhere */ :focus, :hover {   filter: brightness(120%) saturate(120%); }

Direct Link to ArticlePermalink

The post Using a brightness() filter to generically highlight content appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , , ,

How to Create a “Skip to Content” Link

Skip links are little internal navigation links that help users move around a page. It’s possible you’ve never actually seen one before because they’re often hidden from view and used as an accessibility enhancement that lets keyboard users and screen readers jump from the top of the page to the content without have to go through other elements on the page first.

In fact, you can find one right here on CSS-Tricks if you crack DevTools open.

In my opinion, the best way to implement a skip link is to hide it and then bring it into view when it is focused. So, let’s say we have a link in the HTML:

<a class="skip-to-content-link" href="#main">Skip to content</a>

…we can give it an absolute position and translate it off the screen:

.skip-to-content-link {   left: 50%;   position: absolute;   transform: translateY(-100%); }

Then we can bring it back into view when it’s in focus and style it up a bit in the process:

.skip-to-content-link {   background: #e77e23;   height: 30px;   left: 50%;   padding: 8px;   position: absolute;   transform: translateY(-100%);   transition: transform 0.3s; }  .skip-to-content-link:focus {   transform: translateY(0%); }

This will hide our link until it is focused and then put it into view when it becomes focused.

Now, allow me to elaborate, starting with this quote from Miles Davis:

Time isn’t the main thing. It’s the only thing.

As I sit down to write this article in a rainy Ireland, I think of the challenge that many users face as they use the web that I take for granted. We put so much thought into creating a great user experience without thinking of all our users and how to meet their needs. Admittedly, a skip link was something I had never heard of until completing a course on Frontend Masters by Marcy Sutton. Since learning the power and simplicity of using a skip link, I decided to make it my mission to spread more awareness — and what platform better than CSS-Tricks!

A solution is an answer to a problem, so what’s the solution for helping keyboard users and screen readers find the content of a page quickly? In short, the solution is time. Giving users the ability to navigate to parts of our website that they are most interested in gives them the power to save valuable time.

Take the Sky News website as an example. It offers a “Skip to content” button that allows users to skip all the navigation items and jump straight to the main content.

You can see this button by navigating to the top of the page using your keyboard.  This is similar to the implementation shown above. The link is always in the document but only becomes visible when it’s in focus.

This is the type of skip link we are going to create together in this post.

Our sample website

I built a sample website that we will use to demonstrate a skip link.

The website has a number of navigation links but, in the interest of time, there are only two pages: the home page and the blog page. This is all we need to see how things work.

Identifying the problem

Here’s the navigation we’re working with:

In total, we have eight navigation items that a keyboard user and screen reader must tab through before reaching the main content below the navigation.

That’s the problem. The navigation may be irrelevant for the user. Perhaps the user was given a direct link to an article and they simply want to get to the content.

This is a perfect use case for a skip link.

Creating the link

There are a couple of approaches to creating a Skip to content link.  What I like to do is similar to the Sky News example, which is hiding the link until it is focused.  That means we can drop the link at or near the top of the page, like inside the <header> element.

<header>   <a class="skip-link" href='#main'>Skip to content</a>   <!-- Navigation--> </header>

This link has a .skip-link class so we can style it. Thehref attribute points to #main, which is the ID we will add to the <main> element that comes further down the page. That’s what the link will jump to when clicked.

<header>   <a class="skip-link" href='#main'>Skip to content</a>   <!-- Navigation--> </header> 
 <!-- Maybe some other stuff... --> 
 <main id="main">   <!-- Content --> </main>

Here’s what we have if we simply drop the link into the header with no styling.

This does not look great, but the functionality is there. Try navigating to the link with your keyboard and pressing Enter when it’s in focus.

Now it’s time to make it look pretty. We must first fix the positioning and only show our skip link when it is in focus.

.skip-link {   background: #319795;   color: #fff;   font-weight: 700;   left: 50%;   padding: 4px;   position: absolute;   transform: translateY(-100%); }  .skip-link:focus {   transform: translateY(0%); }

The magic here is in the transform property, which is hiding and showing our skip link depending on whether it is focused or not. Let’s make it look a little nicer with a quick transition on the transform property.

.skip-link {   /* Same as  before */   transition: transform 0.3s; }

It will now transition into view which makes that bit better.

You should now (hopefully) have what I have below:

As you can see, the skip link bypasses the navigation and jumps straight down to the <main> element when clicked.

It’s OK to use more than one link!

Right now, the link only serves one purpose and that is skip to the content of our website. But we don’t have to stop there.

We can go even further and create a skip link that has more options, such as a way to jump to the footer of the site. As you might imagine, this is quite similar to what we’ve already done.

Let’s make the blog page of the example site a little more usable by using multiple skip links. It’s common for blogs to use AJAX that loads in more posts when reaching the bottom of the page. This can make it very difficult to get to the footer of the website. That’s the problem we want to solve in this case.

So let’s add a second link that bypasses all that auto-loading business and jumps the user straight to a #footer element on the page.

<div class="skip-link" >   Skip to <a href='#main'>content</a> or <a href='#footer'>footer</a> </div>

We also need to amend our CSS a little and use the :focus-within pseudo selector.

.skip-link {   transform: translateY(-100%); }  .skip-link:focus-within {   transform: translateY(0%); }

This is saying if anything within our .skip-link element has focus, then we show it. Sadly, neither Internet Explorer nor Opera Mini support focus-within, but its coverage is pretty darn good and there is a polyfill available.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.


Chrome Firefox IE Edge Safari
60 52 No 79 10.1

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
80 68 No 10.3

Last thing we need to do is make sure there’s an ID on our footer element so the link has something to jump to.

<footer id="footer">

Here’s what this gives us:

If we wanted to go one step further (and I’d encourage it), we could style each link a little differently so users can distinguish between the two. Both links in this example are plain white which works great for a single button that does a single thing, but it’d be clearer that we’re dealing with two links if they were presented differently.

Jumping to conclusions

Are you using skip links on your site? Or, if not, does this convince you to use one? I hope it’s clear that skip links are a great value add when it comes to the accessibility of a site. While it’s not a silver bullet that solves every accessibility need, it does solve some use cases that make for a much more polished user experience.

Here are some prominent sites that are using this or a similar technique:

The post How to Create a “Skip to Content” Link appeared first on CSS-Tricks.


, , ,

Is Having an RSS Feed Just Giving Content Away for Free?

I mean, kinda.

I was just asked this question the other day so I’m answering here because blogging is cool.

The point of an RSS feed is for people to read your content elsewhere (hence the last part of the acronym, Syndication, as in, broadcasting elsewhere). Probably an RSS reader. But RSS is XML, so in a sense, it’s a limited API to your content as well, which people can use to do other programmatic things (e.g. show a list of recent posts on some other site).

If you hate the idea of people seeing your work outside of your website, then don’t have an RSS feed. It doesn’t prevent your site from being scraped (nothing really does), but it isn’t inviting people to your content the way RSS does.


Don’t you want people to read your stuff? Having an RSS feed is saying, “I’m happy to meet you where you are. If you like reading stuff over there, then great, read it over there. I just like it when you read my stuff.”

It’s hard enough to get people to care about your work anyway. Being extra protective over it isn’t going to help that.

Who’s comic book are you more likely to buy? The webcomic you read and laugh at every day because they make it so easy and free to read? Or the comic that you can’t see because you have to pay for to get a peek and have to roll the dice on whether you’re going to like it or not?

What consultant are you more likely to hire? The one that shares a ton of knowledge about their skills and has firmly established themselves as a publicly verifiable expert? Or a consultant with a homepage that’s just a pricing sheet and phone number?

What blog are you more likely to trust a recommendation from? One that you subscribe to on purpose because you like their content and writers? Or some site you randomly landed on?

What web do you want to exist? One with fun interoperable possibilities? Or walled gardens?

The post Is Having an RSS Feed Just Giving Content Away for Free? appeared first on CSS-Tricks.


, , , , , ,

Embedded Content in Markdown

Markdown supports HTML, so if you need to, say, embed a YouTube video, you can just copy and paste the embed code from them, drop it into a Markdown document, and you should be good to go. With YouTube specifically, there are other options. But in general, you don’t need to do anything special to embed third-party media in Markdown.

You do need to do whatever is necessary for that particular service though. For example, on CodePen, you visit any particular Pen to get the embed code, click “Embed” in the footer, choose options, and ultimately get the embed code. On Twitter, you click a down arrow thingy and choose Embed Tweet, then get forwarded to some other website where you choose options and ultimately get the embed code. It’s different on every service.

That’s the spirit behind gatsby-remark-embedder from Michaël De Boey, which I recently saw. It spells this out:

Trying to embed well known services (like CodePen, CodeSandbox, Slides, SoundCloud, Spotify, Twitter or YouTube) into your Gatsby website can be hard, since you have to know how this needs to be done for all of these different services.

So what this plugin does is allows you to drop a URL to the thing you’re trying to embed on its own line, and it’s magically transformed into embed code. For example, you put a URL to a Pen like this:


…and you get:

<iframe   src="https://codepen.io/team/codepen/embed/preview/PNaGbb"   style="width:100%; height:300px;" ></iframe>

…by the time the content makes its way to the DOM.

As an owner of CodePen, I can’t help but to remind you that doing it this way means you can’t take advantage of having a theme or making it editable. But hey, I get it.

What I think is a smidge funny is that… this is exactly what oEmbed is. The whole spirit of oEmbed is, “Put a URL to a thing on its own line and we’ll try to make it into an embed for you.” It’s a clearly defined spec and there is a clear source of data of sites that support the feature.

But I suppose it’s a failing of oEmbed that people either don’t know about it or don’t use it. Even Embedly seems kinda dead-ish?

The post Embedded Content in Markdown appeared first on CSS-Tricks.


, ,

Weekly Platform News: Impact of Third-Party Code, Passive Mixed Content, Countries with the Slowest Connections

In this week’s roundup, Lighthouse sheds light on third-party scripts, insecure resources will get blocked on secure sites, and many country connection speeds are still trying to catch up to others… literally.

Measure the impact of third-party code during page load

Lighthouse, Chrome’s built-in auditing tool, now shows a warning when the impact of third-party code on page load performance is too high. The pre-existing “Third-party usage” diagnostic audit will now fail if the total main-thread blocking time caused by third-parties is larger than 250ms during page load.

Note: This feature was added in Lighthouse version 5.3.0, which is currently available in Chrome Canary.

(via Patrick Hulce)

Passive mixed content is coming to an end

Currently, browsers still allow web pages loaded over a secure connection (HTTPS) to load images, videos, and audio over an insecure connection. Such insecurely-loaded resources on securely-loaded pages are known as “passive mixed content,” and they represent a security and privacy risk.

An insecurely-loaded image can allow an attacker to communicate incorrect information to the user (e.g., a fabricated stock chart), mutate client-side state (e.g., set a cookie), or induce the user to take an unintended action (e.g., changing the label on a button).

Starting next February, Chrome will auto-upgrade all passive mixed content to https:, and resources that fail to load over https: will be blocked. According to data from Chrome Beta, auto-upgrade currently fails for about 30% of image loads.

(via Emily Stark)

Fast connections are still not common in many countries

Data from Chrome UX Report shows that there are still many countries and territories in the world where most people access the Internet over a 3G or slower connection. (This includes a number of small island nations that are not visible on this map.)

(via Paul Calvano)

More news…

Read even more news in my weekly Sunday issue that can be delivered to you via email every Monday morning.

More News →

The post Weekly Platform News: Impact of Third-Party Code, Passive Mixed Content, Countries with the Slowest Connections appeared first on CSS-Tricks.


, , , , , , , , , , ,