Grow your CSS skills. Land your dream job.

CSS :target for Off-Screen Designs

Published by Guest Author

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


  1. Permalink to comment#

    Nice demo but few notes

    – the ~ selector (used with :checked) is not supported by android 2.1 (“where is the navigation/sidebar ?”) (+ is supported)

    – target and back button (history) can do some “strange” behaviour for end user (
    in the demo the back button won’t be used because of the fixed poisitioned header but fixed and mobile… On a “real” website it may causes some trouble, more for a sidebar than a nav )

  2. Brilliant, especially the chaining of :checked, it blew my mind !
    Thanks a lot for sharing such a wonderful demo Chris and Matty. :)

  3. Nice post, I will look into this one as I too like the simplicity of html mixed with some nice css features to make stuff happen.

  4. Since support for :target seems a little mixed, wouldn’t it be better to use something like jQuery scrollTo or a CSS animation instead?

    • Permalink to comment#

      My preference would be using jQuery to assign a class to the navigation and use CSS transitions, but it could be used to enhance the experience for mobile non-JS users. A fall-back for non :target support would be ideal, but how you could find that out without JavaScript?….I am unsure

  5. Permalink to comment#

    Nice one.. i will try

  6. That is very cool!

  7. Bobby
    Permalink to comment#

    another good post from you Chris. Totally will work out something from this. thx!

  8. This is a pretty cool technique, unfortunately, in practice, I find so many of these really slick CSS techniques to fall a bit short. For instance, on Android 4.0, clicking a message original message list often failed to bring the conversation into view.

    In my experience, browsers that support advanced selectors and transitions also support jquery. While jquery adds some weight to the page, I find it more reliable.

    Still, an interesting fall back and exercise, and for that, I thank you.

  9. It’s a really interesting technique, but as others have said, :target is not quite reliable enough to make this practical. I suspect (without any evidence to back it up) that javascript is more widely available and therefore more practical.

    My preference is using javascript to assign / remove a class, use native CSS transitions, and provide a fall-back where javascript is unavailable. I’m sure this still fails on some devices, but it seems reasonably reliable.

    Having said that, I really love this kind of idea and article. I’m always enormously impressed at the creativity of other designers and developers.

  10. Permalink to comment#

    I love the fact that it supports Magic Mouse swiping to move between the messages and the inbox. Where does that support come from?

  11. Permalink to comment#

    Thanks Chris, nice demo

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".