CSS :target for Off-Screen Designs

Avatar of Matty Collins
Matty Collins on (Updated on )

The following is a guest post by Matty Collins. Matty wrote to me to share a little demo he cooked up which recreates the experience of using the Message app on an iPhone. Perhaps more interesting than the recreation of that UI is that the technique is very simple, requires no JavaScript (the “Checkbox Hack”, and tackles an often difficult question on small screens: how do we handle navigation? I asked Matty if he’d like to write a guest post and he obliged.

I recently saw a presentation by Ryan Seddon on things you can do with checkboxes. It got me thinking about a similar pseudo selector based on the URL, :target, and how it could be used for hiding and revealing elements for extra space (namely on smaller screens). I set myself the task of recreating the iOS Message app without using JavaScript by taking full advantage of awesome CSS3 selectors and transitions.

Simple Example

The following code examples show how we could hide a login box and navigation, exposing them when needed.

<header id="hd">
  <a href="/" class="logo">Company logo</a>
      <li><a href="#">Home</a></li>
      <li><a href="#">News</a></li>
      <li><a href="#">About</a></li>
      <li><a href="#">Contact</a></li>
  <a href="#login">Log in</a>
  <div id="login">
      <label for="username">Username:</label>
      <input type="textbox" id="username">
      <label for="password">Password:</label>
      <input type="password" id="password">
      <input type="submit" value="login"/>
      <a href="/register">Sign up</a>
<div id="bd">
      ... lorem ipsum content ...
  <a href="#auxNav">Internal navigation</a>
  <nav id="auxNav">
      <li><a href="#">First link</a></li>
      <li><a href="#">Second link</a></li>
      <li><a href="#">Another link</a></li>
body { 
  position: relative; 
  max-width: 960px; 
  margin: 0 auto; 

#login { 
  position: absolute;
  top: -200px;
  right: 0;
  -webkit-transition: top 0.3s ease-in-out;
  -moz-transition:    top 0.3s ease-in-out;
  -ms-transition:     top 0.3s ease-in-out;
  -o-transition:      top 0.3s ease-in-out;
  transition:         top 0.3s ease-in-out;

#bd { 
  position: relative;
  overflow: hidden;

#auxNav {
  position: relative;
  overflow: hidden;
  height: 60px; 

#auxNav ul {
  position: absolute;
  left: -100%;
  -webkit-transition: left 0.3s ease-in-out;
  -moz-transition:    left 0.3s ease-in-out;
  -ms-transition:     left 0.3s ease-in-out;
  -o-transition:      left 0.3s ease-in-out;
  transition:         left 0.3s ease-in-out;

/* The Magic Part */
#login:target { 
  top: 0;

#auxNav:target ul {
  left: 0;

By starting the navigation and login offscreen first, we can then reveal them on :target. Note how the anchor link’s href attribute “#auxNav” matches the ID of the nav, “auxNav”.

The auxNav shows that we don’t need to hide the ID itself, but can hide any decendants of it.

The “App”

Each message (“screen”) has an ID and is positioned off screen. When the anchor link is clicked, the hash link in the URL changes, the :target selector matches, and the appropriate screen shows.

I used the :checked selector to toggle the editing of a message. Each message in the list is an anchor to its full detail version. The tiny bit of code :target { left: 0; } is all that was needed to bring them into view.

A hidden “delete” button slides in when the edit button is selected. The button text is changed via content in the :before pseudo element, rather than creating another element to swap states. The “delete” message button simply hides the next message in the list when it is checked.

Each message was then shown upon the :target selector matching, sliding in from the left (there were difficulties with transitioning from the right). A more complex chaining of checkboxes was used to slide in checkboxes for each thread in the message (while slightly altering its dimensions) and exposing another checkbox at the bottom to confirm the deletion. Using the same method as before, the message was hidden via :checked + div { display: none; }

View demo

(Demo works on mobile but it’s more of a desktop experience – making it fit a mobile screen would be trivially easy.)


  • No JavaScript!
  • Only an anchor link, ID and a simple pseudo selector is needed to achieve this
  • Isn’t completely reliant on markup order. Each object just needs an ID, or ID in the selector. Eg: #hd:target nav
  • Falls back to anchor when unsupported
  • Auto scrolls to element so fixed position isn’t required

Disadvantages / Issues

  • Only seems to transition in and out when positioned off the left hand side, others only transition after leaving :target
  • Only one element can feature on the screen
  • Using a right offset completely confused Chrome, would falsely show the last ID as targeted


I’m always interested in what we can create with only markup and CSS. While this is a hack and may not be ideal for production use, at the very least it shows we don’t have to compromise design or interactivity if the device/user is lacking JavaScript and can fall back to simply scrolling the desired element into view.

Related Links