Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save ja-k-e/9db1fa91f15fed5a8038 to your computer and use it in GitHub Desktop.

Select an option

Save ja-k-e/9db1fa91f15fed5a8038 to your computer and use it in GitHub Desktop.
CSS-Only Presentation Using `:target` and `accesskey`

CSS-Only Presentation Using :target and accesskey

In one of those “inaccessible CSS solution” moods. If you’re asking “should I do this?” you probably shouldn’t.

Oddly slow in latest Chrome on PC, works fine in FF.

A Pen by Jake Albaugh on CodePen.

License.

<a id=s1 title="Section 1 Anchor" class=s></a>
<a id=s2 title="Section 2 Anchor" class=s></a>
<a id=s3 title="Section 3 Anchor" class=s></a>
<a id=s4 title="Section 4 Anchor" class=s></a>
<a id=s5 title="Section 5 Anchor" class=s></a>
<a id=s6 title="Section 6 Anchor" class=s></a>
<a id=s7 title="Section 7 Anchor" class=s></a>
<div id=progress></div>
<div id=background></div>
<nav class=prevnext role=presentation>
<ul>
<li class=p2><a href=#s1 accesskey=1 title="Trigger Section 1: This Page Refuses to Use Javascript"></a></li>
<li class="p3n1 starter"><a href=#s2 accesskey=2 title="Trigger Section 2: :target Pseudo Selector"></a></li>
<li class=p4n2><a href=#s3 accesskey=3 title="Trigger Section 3: ~ Swinton"></a></li>
<li class=p5n3><a href=#s4 accesskey=4 title="Trigger Section 4: Getting Cray / On Fleek"></a></li>
<li class=p6n4><a href=#s5 accesskey=5 title="Trigger Section 5: Accesskeys"></a></li>
<li class=p7n5><a href=#s6 accesskey=6 title="Trigger Section 6: Limitations"></a></li>
<li class=n6><a href=#s7 accesskey=7 title="Trigger Section 7: Acceptable Use Cases"></a></li>
</ul>
</nav>
<nav class=thumbs aria-label="table of contents">
<ul>
<li><a href=#s1 title="Thumbnail Trigger Section 1: This Page Refuses to Use Javascript"></a></li>
<li><a href=#s2 title="Thumbnail Trigger Section 2: :target Pseudo Selector"></a></li>
<li><a href=#s3 title="Thumbnail Trigger Section 3: ~ Swinton"></a></li>
<li><a href=#s4 title="Thumbnail Trigger Section 4: Getting Cray / On Fleek"></a></li>
<li><a href=#s5 title="Thumbnail Trigger Section 5: Accesskeys"></a></li>
<li><a href=#s6 title="Thumbnail Trigger Section 6: Limitations"></a></li>
<li><a href=#s7 title="Thumbnail Trigger Section 7: Acceptable Use Cases"></a></li>
</ul>
</nav>
<main role=main>
<section>
<article>
<h1>This pen refuses to use Javascript</h1>
<p>To navigate, you can use the prev/next buttons or the thumbnails to the right. If you prefer to get crazy, you can also use <code>accesskey</code> to jump to sections by using the following keyboard shortcuts:</p>
<pre>
Mac: alt + ctrl + [1-7]
PC Chrome/IE: alt + [1-7]
PC FF: alt + shift + [1-7]</pre>
<p class=right><small><a href="http://www.w3schools.com/tags/att_global_accesskey.asp" target="blank">More on accesskeys</a></small></p>
</article>
</section>
<section>
<article>
<h1>:target</h1>
<p>This interface relies on the CSS <code>:target</code> selector. An element is a “target” when its <code>id</code> is hash-referenced in the url.</p>
<pre>&lt;a href="http://example.com#section-1"&gt;Section 1&lt;/a&gt;</pre>
<pre>#section-1:target {
display: block;
}</pre>
<p>In the above example, whenever the link is clicked, the content with an <code>id</code> of <code>section-1</code> will be displayed.</p>
<p>CodePen removes the hash from the visible url, but right now you are looking at this pen with <code>#s2</code> as the target. If you were to manually add <code>#s2</code> to the end of this url and reload this pen, you will be taken to this screen instead of the first.</p>
</article>
</section>
<section>
<article>
<h1>~ Swinton</h1>
<p>In CSS, the tilde <code>~</code> is used as the “General sibling combinator”. In English, that means “any following sibling”.</p>
<pre>.a ~ .b {
display: none;
}</pre>
<p>This would hide any <code>.b</code> element that comes after an <code>.a</code> and is a sibling.</p>
<pre>
&lt;div class="b"&gt;All good&lt;/div&gt;
&lt;div class="a"&gt;All good&lt;/div&gt;
&lt;div class="x"&gt;
&lt;div class="b"&gt;Still good&lt;/div&gt;
&lt;/div&gt;
&lt;div class="b"&gt;Hidden&lt;/div&gt;
</pre>
<p>Only the last <code>.b</code> would be hidden. The first comes before the <code>.a</code>, and the second is not a direct sibling.</p>
</article>
</section>
<section>
<article>
<h1>Getting Cray / On Fleek</h1>
<p>By using <code>:target</code> and <code>~</code> we can do some pretty ridiculous things without touching Javascript. In this pen, we are using <code>:target</code> to trigger different state-related styles.</p>
<p>At the top of this pen, there are <code>&lt;span&gt;</code> elements. Their only purpose is being used as targets.</p>
<pre>
&lt;span id="s1" class="s"&gt;&lt;/span&gt;
&lt;span id="s2" class="s"&gt;&lt;/span&gt;
...
</pre>
<p>Using their <code>:target</code> state, we can then select and style anything else on the page.</p>
<p>Moving the main content area over <code>-300%</code> to reveal this fourth section.</p>
<pre>#s4:target ~ main {
left: -300%;
}</pre>
<p>Changing the background color and image:</p>
<pre>#s4:target ~ #background {
background-image: url(http://lorempixel.com/g/400/150/city/4/);
background-color: #265273;
}</pre>
<p>Changing the progress bar width:</p>
<pre>#s4:target ~ #progress {
width: 50%;
}</pre>
<p>Styling the fourth thumbnail as active:</p>
<pre>#s4:target ~ .thumbs li:nth-child(4) a {
opacity: 1;
width: 80px;
height: 60px;
box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.1);
}</pre>
<p>Showing the appropriate previous link and making it say the right thing:</p>
<pre>#s4:target ~ .prevnext li[class*="p4"] {
display: block;
float: left;
}
#s4:target ~ .prevnext li[class*="p4"] a:after {
content: "Prev";
}</pre>
<p>Showing the appropriate next link and making it say the right thing:</p>
<pre>#s4:target ~ .prevnext li[class*="n4"] {
display: block;
float: right;
}
#s4:target ~ .prevnext li[class*="n4"] a:after {
content: "Next";
}</pre>
</article>
</section>
<section>
<article>
<h1>accesskeys</h1>
<p>Each of the next/prev navigation items link to each of these spans and have an <code>accesskey</code> attribute. This allows them to be fired using the browser’s <code>accesskey</code> plus whatever key is provided. In this case, we are using the corresponding section number.</p>
<pre>
&lt;a href="#s1" accesskey="1"&gt;&lt;/a&gt;
&lt;a href="#s2" accesskey="2"&gt;&lt;/a&gt;
...
</pre>
<p>Each browser and operating system use different keyboard shortcuts for <code>accesskey</code>, but the support is suprisingly universal.</p>
<pre>Mac: alt + ctrl
PC Chrome/IE: alt
PC FF: alt + shift</pre>
<p>To switch to the next section, you can hold down your <code>accesskey</code> combination above and then press <code>6</code>.
<p>There’s more to read about <code>accesskey</code> on <a href="http://www.w3schools.com/tags/att_global_accesskey.asp" target="blank">w3schools</a>.</p>
</article>
</section>
<section>
<article>
<h1>Limitations</h1>
<p>As with any “Who needs JS?” approach, there are more than a few things to consider when doing something like this.</p>
<h2>Accessibility</h2>
<p>Confining yourself to CSS makes any interactive experience more inaccessible. There are a handful of ways one could optimize this for screen readers more than I (hardly) have, but it would be a lot of effort for a unsatisfactory result.</p>
<p>Additionally, since the “prev/next” links are actually new links on each section change, the <code>:focus</code> that occurs when you have tabbed into them disappears because the link is actually disappearing. You need to re-tab in every time. Blech. No one likes excess tab unless we’re talking about <a href="https://en.wikipedia.org/wiki/Tab_(soft_drink)" title="Wikipedia: Tab Soda" target="blank">the soda</a>.</p>
<h2>History</h2>
<p>Each hashchange is going to be recorded in the browser history without some Javascript intervention. This can go either way as a pro or con. For something like this, it may be a pro.</p>
<h2>Scalability</h2>
<p>All ths content is being dumped into the DOM at once. At a certain size, this approach might not make a lot of sense. In theory, you could get around some of this with some smart selectors and <code>display: none;</code>, but depending on the complexity of your content, you may still have some load time issues. Dynamically loading content with Javascript is a often a very, very good thing.</p>
</article>
</section>
<section>
<article>
<h1>Acceptable Use Cases</h1>
<p>There are some cases where something this inaccessible is technically ok. This could make sense in an environment where it is only being used in a visual setting such as a live presentation or internal documentation tool.</p>
<p>Most importantly, it can be used as a way to show that CSS is very powerful, but can also be very limiting—depending on the need.</p>
<p>If you liked this, you may enjoy more of my bogus CSS in my <a href="http://codepen.io/collection/DzxNzN/" target="blank">No JS Collection</a>.</p>
</article>
</section>
</main>
@import url(https://fonts.googleapis.com/css?family=Merriweather:300,300italic|Inconsolata);
$border-color: rgba(#FFF,0.1);
$mobile-w: 600px;
$large-w: 1200px;
$thumb-w: 80px;
$thumb-h: 60px;
$thumb-pad: 10px;
$s-count: 7;
main {
position: absolute;
top: 0; left: 0; bottom: 0;
width: 100%;
transition: left 500ms;
}
//
// each section
section {
position: absolute;
top: 0; right: 0; bottom: 0;
width: 100%;
margin: 0;
box-sizing: border-box;
overflow: scroll;
-webkit-overflow-scrolling: touch;
transition: opacity 250ms ease-in-out;
article {
width: calc(90% - #{$thumb-w + $thumb-pad});
max-width: 800px;
padding: 4rem 0 4rem 5%;
color: #fff;
}
code, pre {
background: rgba(#000,0.5);
border: 1px solid $border-color;
border-radius: 4px;
box-sizing: border-box;
}
code {
padding: 0.25rem 0.5rem;
}
pre {
padding: 1rem;
margin: 1rem auto;
}
}
// main nav ids
.s {
position: absolute;
}
//
// navigation mixin styles
@mixin active-next() {
display: block;
float: right;
a:after { content: "Next"; }
}
@mixin active-prev() {
display: block;
float: left;
a:after { content: "Prev"; }
}
@mixin active-thumb {
opacity: 1;
width: $thumb-w;
height: $thumb-h;
box-shadow: 0px 0px 0px 1px $border-color;
}
@mixin inactive-thumb {
opacity: 0.5;
width: $thumb-w / 2;
height: $thumb-h / 2;
box-shadow: 0px 0px 0px 1px rgba(#FFF,0);
}
//
// navigation thumbs and buttons
nav {
position: fixed;
z-index: 1;
width: 100%;
user-select: none;
&.thumbs {
right: $thumb-pad;
top: 50%;
transform: translateY(-50%);
width: $thumb-w + $thumb-pad;
display: block;
ul {
margin: 0; padding: 0;
}
li {
display: block;
margin: 0 auto 0.5rem;
&:first-child a {
@include active-thumb();
}
a {
&:hover {
opacity: 1;
}
@include inactive-thumb();
margin: 0 auto;
display: block;
background-blend-mode: multiply;
background-position: center;
background-size: cover;
border-radius: 2px;
position: relative;
transition:
height 250ms ease-in-out,
width 250ms ease-in-out,
opacity 250ms ease-in-out,
box-shadow 250ms ease-in-out;
&:active {
transform: translateY(1px);
}
}
}
}
&.prevnext {
left: 0;
top: 1rem;
ul {
list-style: none;
margin: 0.5rem auto;
padding: 0;
width: 220px;
position: relative;
&:after {
content: "";
display: table;
clear: both;
}
li {
display: none;
width: 100px;
&.starter {
@include active-next();
}
}
a {
display: block;
width: 100%;
box-sizing: border-box;
padding: 0.5rem 1rem;
border-radius: 4px;
text-align: center;
box-shadow: 0px 0px 0px 0px $border-color;
border: 1px solid $border-color;
color: #FFFFFF;
background-color: rgba(#000,0.4);
transition: background 200ms ease-in-out 200ms,
box-shadow 200ms ease-in-out;
&:hover, &:active {
background-color: rgba(#000,0.5);
}
&:hover {
box-shadow: 0px -2px 0px 0px $border-color;
}
&:active {
box-shadow: 0px 0px 0px 0px $border-color;
transform: translateY(1px);
}
}
}
}
a {
text-decoration: none;
}
}
//
// slider experience on non mobile
@media (min-width: $mobile-w) {
main {
height: 100%;
}
}
//
// content position on large
@media (min-width: $large-w) {
main article {
padding-left: 0;
margin: 0 auto;
}
}
//
// prevnext buttons on non mobile
@media (min-width: $mobile-w) {
nav.prevnext {
top: auto;
bottom: 1rem;
}
}
//
// progress indicator at top of page
#progress {
position: fixed;
z-index: 9;
top: 0;
left: 0;
width: 0%;
border-bottom: 12px solid rgba(#FFF,0.2);
transition: width 1250ms linear;
}
//
// :target and other iterative styles
@for $i from 1 through $s-count {
//
// adjusting section opacity on all screens
#s#{$i}:target ~ main section {
opacity: 0;
&:nth-child(#{$i}) {
opacity: 1;
}
}
//
// adjusting section position on mobile screens
@media (max-width: $mobile-w - 1) {
#s#{$i}:target ~ main section {
top: 50%;
&:nth-child(#{$i}) {
top: 0;
}
}
}
//
// adjusting left position on big screens
@media (min-width: $mobile-w) {
// parent shift on target
#s#{$i}:target ~ main {
left: -100% * ($i - 1);
}
// individual
section:nth-child(#{$i}) {
left: 100% * ($i - 1);
}
}
.thumbs li:nth-child(#{$i}) a {
background-image: url(http://lorempixel.com/g/400/150/city/#{$i}/);
background-color: hsl($i / $s-count * 360, 50, 30);
}
#s#{$i}:target ~ #background {
background-image: url(http://lorempixel.com/g/400/150/city/#{$i}/);
background-color: hsl($i / $s-count * 360, 50, 30);
}
#s#{$i}:target ~ #progress {
width: ($i - 1) / ($s-count - 1) * 100%;
}
#s#{$i}:target ~ .thumbs li:first-child a {
@include inactive-thumb();
}
#s#{$i}:target ~ .thumbs li:nth-child(#{$i}) a {
@include active-thumb();
}
#s#{$i}:target ~ .prevnext li.starter {
display: none;
}
#s#{$i}:target ~ .prevnext li[class*="p#{$i}"] {
@include active-prev();
}
#s#{$i}:target ~ .prevnext li[class*="n#{$i}"] {
@include active-next();
}
}
//
// background image
#background {
background-image: url(http://lorempixel.com/g/400/150/city/1/);
background-color: hsl(1 / 5 * 360, 50, 30);
background-blend-mode: multiply;
background-position: center;
background-size: cover;
$blur: 10px;
filter: blur($blur);
position: fixed;
z-index: -1;
transition:
background-image 500ms ease-in-out 500ms,
background-color 500ms ease-in-out 1000ms;
top: $blur * -2; right: $blur * -2; bottom: $blur * -2; left: $blur * -2;
}
//
// global non-essential styles
html, body {
height: 100%;
width: 100%;
overflow: hidden;
color: #FFF;
font-family: Merriweather, Georgia, serif;
font-weight: 300;
}
pre, code {
font-family: Inconsolata, monospace;
}
a {
color: #EEE;
}
h1, p {
font-weight: 300;
}
p, ul {
line-height: 1.6;
}
.right { text-align: right; }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment