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>
<nav>
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">News</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
<a href="#login">Log in</a>
<div id="login">
<fieldset>
<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>
</fieldset>
</div>
</header>
<div id="bd">
<section>
<p>
... lorem ipsum content ...
</p>
</section>
<a href="#auxNav">Internal navigation</a>
<nav id="auxNav">
<ul>
<li><a href="#">First link</a></li>
<li><a href="#">Second link</a></li>
<li><a href="#">Another link</a></li>
</ul>
</nav>
</div>
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; }
(Demo works on mobile but it’s more of a desktop experience – making it fit a mobile screen would be trivially easy.)
Advantages
- 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
Conclusion
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
- Aaron Gustafson: Build smart mobile navigation without hacks
- Luke Wroblewski: multi-device layout patterns
- Brad Frost: responsive navigation patterns
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 )
Brilliant, especially the chaining of :checked, it blew my mind !
Thanks a lot for sharing such a wonderful demo Chris and Matty. :)
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.
Since support for :target seems a little mixed, wouldn’t it be better to use something like jQuery scrollTo or a CSS animation instead?
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 unsureNice one.. i will try
That is very cool!
another good post from you Chris. Totally will work out something from this. thx!
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.
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.
I love the fact that it supports Magic Mouse swiping to move between the messages and the inbox. Where does that support come from?
Thanks Chris, nice demo