Skip to content

Instantly share code, notes, and snippets.

Created June 28, 2014 14:45
Show Gist options
  • Save anonymous/3f17757b56c0686d415a to your computer and use it in GitHub Desktop.
Save anonymous/3f17757b56c0686d415a to your computer and use it in GitHub Desktop.
A Pen by Jake Albaugh.
<!-- TAB CONTROLLERS -->
<input id="panel-1-ctrl"
class="panel-radios" type="radio" name="tab-radios" checked>
<input id="panel-2-ctrl"
class="panel-radios" type="radio" name="tab-radios">
<input id="panel-3-ctrl"
class="panel-radios" type="radio" name="tab-radios">
<input id="panel-4-ctrl"
class="panel-radios" type="radio" name="tab-radios">
<input id="panel-5-ctrl"
class="panel-radios" type="radio" name="tab-radios">
<input id="nav-ctrl"
class="panel-radios" type="checkbox" name="nav-checkbox">
<header id="introduction">
<h1>No JS: Tabs That Scale Down to Menu</h1>
</header>
<!-- TABS LIST -->
<ul id="tabs-list">
<!-- MENU TOGGLE -->
<label id="open-nav-label" for="nav-ctrl"></label>
<li id="li-for-panel-1">
<label class="panel-label"
for="panel-1-ctrl">Radio Toggles</label>
</li><!--INLINE-BLOCK FIX
--><li id="li-for-panel-2">
<label class="panel-label"
for="panel-2-ctrl">Input :checked</label>
</li><!--INLINE-BLOCK FIX
--><li id="li-for-panel-3">
<label class="panel-label"
for="panel-3-ctrl">The Tabs</label>
</li><!--INLINE-BLOCK FIX
--><li id="li-for-panel-4">
<label class="panel-label"
for="panel-4-ctrl">Tab :hover</label>
</li><!--INLINE-BLOCK FIX
--><li id="li-for-panel-5">
<label class="panel-label"
for="panel-5-ctrl">Menu</label>
</li>
<label id="close-nav-label" for="nav-ctrl">Close</label>
</ul>
<!-- THE PANELS -->
<article id="panels">
<div class="container">
<section id="panel-1">
<main>
<h1>Radio Toggles</h1>
<p>In this demo, <code>labels</code> for hidden <code>radios</code> toggle the content. This is based on the behavior in which clicked <code>labels</code> for a <code>radio</code> or <code>checkbox</code> input will check that <code>input</code>.</p>
<pre>&lt;input id="radio-1" type="radio" name="demo-radios"&gt;
&lt;input id="radio-2" type="radio" name="demo-radios"&gt;</pre>
#radio-1: <input id="radio-1" type="radio" name="demo-radios"> #radio-2: <input id="radio-2" type="radio" name="demo-radios">
<pre>&lt;label for="radio-1"&gt;Toggle #radio-1&lt;/label&gt;
&lt;label for="radio-2"&gt;Toggle #radio-2&lt;/label&gt;</pre>
<label for="radio-1" class="demo-label">Toggle #radio-1</label>
<label for="radio-2" class="demo-label">Toggle #radio-2</label>
<p>Click one of the labels above and see its effect on the radios above it.</p>
<p>The radios for this pen's tabs are displayed semi-transparently at the top of this demo page.</p>
</main>
</section>
<section id="panel-2">
<main>
<h1>Input :checked</h1>
<p>In CSS, you can query based on the <code>:checked</code> selector for <code>radios</code> and <code>checkboxes</code> to style siblings down the DOM scope. To do this, we can use the <code>~</code>. It will select same-level siblings after the given selector. Because the tab <code>labels</code> in this demo are nested and not immediate siblings, we will need to select their topmost parent that is at the same level as our <code>input</code>.</p>
<p>To demonstrate, we will do a simplified version of this with a checkbox:</p>
<pre><strong>&lt;!-- invisible input and its label --&gt;</strong>
&lt;input id="demo-child-toggle" type="checkbox"&gt;
&lt;label for="demo-child-toggle"&gt;Toggle #demo-child&lt;/label&gt;
<strong>&lt;-- parent to select first via "~" --&gt;</strong>
&lt;div id="demo-parent"&gt;
<strong>&lt;-- child to select through parent --&gt;</strong>
&lt;div id="demo-child"&gt;#demo-child&lt;/div&gt;
&lt;/div&gt;</pre>
<p>and in our CSS:</p>
<pre><strong>/* hiding our checkbox */</strong>
#demo-child-toggle {
display: none;
}
<strong>/* selecting the child */</strong>
#demo-child-toggle:checked ~ #demo-parent #demo-child {
color: #c0392b;
font-weight: bold;
text-transform: uppercase;
}</pre>
<hr>
<input id="demo-child-toggle" type="checkbox">
<label class="demo-label" for="demo-child-toggle">Toggle #demo-child</label>
<div id="demo-parent">
<div id="demo-child">#demo-child</div>
</div>
<hr>
<p>As you can see, we can control the style of content that comes after a hidden input by toggling it via its label.</p>
<p>At this point you can probably get the picture for how we can conditionally display the tabbed panel content in this pen.</p>
</main>
</section>
<section id="panel-3">
<main>
<h1>The Tabs</h1>
<p>Here is the basic form of a tab in this demo:</p>
<pre>&lt;li id="li-for-panel-1"&gt;
&lt;label class="panel-label" for="panel-1-ctrl"&gt;CSS Radio Toggles&lt;/label&gt;
&lt;/li&gt;</pre>
<p>For the "active" tab to cover the bottom border, the child <code>label</code> gets an additional 2 pixels of <code>padding-top</code> while its parent <code>li</code> gets a <code>translateY(1px)</code>. This not only covers the bottom border, but gives an ever-so-subtle "moving toward you" effect by shifting the title down <code>1px</code>.</p>
<pre>#panel-1-ctrl:checked ~ #tabs-list #li-for-panel-1 {
transform: translate(0, 1px);
}
#panel-1-ctrl:checked ~ #tabs-list #li-for-panel-1 label.panel-label {
padding-top: 26px; <strong>/* instead of "24px" */</strong>
}</pre>
</main>
</section>
<section id="panel-4">
<main>
<h1>Tab :hover</h1>
<p>When designing the <code>:hover</code> and "active" states I had a dilemma.</p>
<pre>&lt;li id="li-for-panel-1"&gt;
&lt;label class="panel-label" for="panel-1-ctrl"&gt;CSS Radio Toggles&lt;/label&gt;
&lt;/li&gt;</pre>
<p>Each tab <code>li</code> has a <code>border-right</code>. But when the additional <code>border-top</code> appears, we dont want the lighter <code>border-right</code> to be shown all the way to the top. The fix for this is to cancel the <code>border-right</code> on both the <code>:hover</code> and "active" state as well as style the <code>li</code>'s next sibling's <code>border-left</code>.</p>
<p>To do this, we can use a combination of the siblings after <code>~</code> and sibling next <code>+</code> selectors:</p>
<pre><strong>/* remove the right border on "active" state */</strong>
#panel-1-ctrl:checked ~ #tabs-list #li-for-panel-1 {
border-right: none;
}
<strong>/* override that if the last child (we still want the border) */</strong>
#panel-1-ctrl:checked ~ #tabs-list #li-for-panel-1:last-child {
border-right: 1px solid #dfdfdf;
}
<strong>/* add left to next sibling */</strong>
#panel-1-ctrl:checked ~ #tabs-list #li-for-panel-1 + li {
border-left: 1px solid #dfdfdf;
}</pre>
</main>
</section>
<section id="panel-5">
<main>
<h1>Menu</h1>
<p>On small screens, the tabs fold down into an expandable menu. To trigger the menu, I use a <code>checkbox</code> (note that it appears at the top of the screen on smaller screen sizes). There are two labels that trigger this checkbox. One opens and the other closes the menu. The one that opens is absolutely positioned invisibly over the "active" menu item. The closing label is at the bottom of the open menu.</p>
<p>The best way I have found to show and hide content without using absolute positioning is to use a combination of <code>max-height</code> and <code>opacity</code>. When "inactive", the content has a <code>max-height: 0</code> and <code>opacity: 0</code>.</p>
<p>It also has a <code>transition: opacity</code> when I don't know the future height (this panel's content for example) and <code>transition: opacity, max-height</code> when I do know the future height (like the menu). When "active", the <code>max-height</code> and <code>opacity</code> get positive values and the content will transition in. I'm sure flexbox could get me around this hack, but this works for now.</p>
</main>
</section>
</div>
</article>

No JS: Tabs that scale down to menu

A tab-to-menu layout that doesn't use javascript. Modern browser compatible.

A Pen by Jake Albaugh on CodePen.

License.

/*
jakealbaugh.com
@jake_albaugh
*/
@import url(http://fonts.googleapis.com/css?family=Open+Sans:300,400|Inconsolata)
/////////////////
// VARIABLES
/////////////////
$tab-count: 5
$menu-item-height: 46px
$color-primary-light: #E74C3C
$color-primary-dark: #C0392B
$color-text: #444
$color-border: #dfdfdf
$color-border-top: $color-primary-dark
$color-label-bg-inactive: #ECF0F1
$color-label-bg-active: #fff
$color-label-active: $color-border-top
$color-label-inactive: #BDC3C7
$btn-border-top-thickness: 6px
$tab-padding: 24px
$tab-transition-duration: 200ms
$tab-base-border-style: 1px solid $color-border
$panel-transition-duration: 600ms
$main-padding: 48px 24px
$menu-padding: 12px 24px
/////////////////
// ELEMENTS
/////////////////
// global list style
ul#tabs-list
list-style: none
text-align: center
border-bottom: $tab-base-border-style
margin: 0
padding: 0
// global label style
label.panel-label
user-select: none
display: block
width: 100%
color: $color-label-inactive
cursor: pointer
background-color: $color-label-bg-inactive
transition-property: border-top, background-color, color
transition-duration: $tab-transition-duration
// hover style
&:hover
color: $color-label-active
// global panel style
#panels
background-color: $color-label-bg-active
.container
margin: 0 auto
width: 90%
section
header
label.panel-label
padding: $menu-padding
box-sizing: border-box
main
box-sizing: border-box
max-height: 0
opacity: 0
transition: opacity $panel-transition-duration
overflow-y: hidden
/////////////////
// MIXINS
/////////////////
// tabs list style
=tabs-list-style
text-align: center
border-bottom: $tab-base-border-style
// li child
li
display: inline-block
text-align: center
font-size: 0.875em
width: (100% / $tab-count) - 2%
box-shadow: 0px -2px 2px rgba(0, 0, 0, 0.05)
border-top: $tab-base-border-style
border-right: $tab-base-border-style
transition-property: border-top
transition-duration: $tab-transition-duration
// hover
&:hover
border-top: none
border-right: none
&:last-of-type
border-right: $tab-base-border-style
& + li
border-left: $tab-base-border-style
// tab labels
label.panel-label
border-top: 0px solid $color-border-top
padding: $tab-padding 0
&:hover
border-top-width: $btn-border-top-thickness
padding-top: $tab-padding + 1
// global active label styles
=active-label
background-color: $color-label-bg-active
color: $color-border-top
// active tab label style
=active-tab-label
border-top: $btn-border-top-thickness solid $color-border-top
// add extra pixel for shifting li downward over border
padding-top: $tab-padding + 2
// active tab li style
=active-tab-li-style
pointer-events: none
cursor: default
// shift down over border. label has extra pixel in padding.
transform: translate(0, 1px)
box-shadow: none
border-top: none
border-right: none
&:last-child
border-right: $tab-base-border-style
& + li
border-left: $tab-base-border-style
label.panel-label
+active-label
+active-tab-label
// active menu label style
=active-menu-label
background-color: $color-label-active
color: $color-label-bg-active
// active menu li style
=active-menu-li-style
max-height: $menu-item-height
opacity: 1
label.panel-label
+active-label
+active-menu-label
// active panel style
=active-panel-style
main
max-height: initial
opacity: 1
padding: $main-padding
// panel toggling
@for $i from 1 through $tab-count
#panel-#{$i}-ctrl:checked ~ #panels #panel-#{$i}
+active-panel-style
/////////////////
// MEDIA QUERIES
/////////////////
// small devices
@media all and (max-width: 767px)
// displaying menu nav
@for $i from 1 through $tab-count
#nav-ctrl:checked ~ #tabs-list #li-for-panel-#{$i}
max-height: $menu-item-height
opacity: 1
// "show" open nav label when closed, hide when open
#open-nav-label
display: block
position: absolute
top: 0
right: 0
bottom: 0
left: 0
cursor: pointer
//background: rgba(0,0,0,0.6)
#nav-ctrl:checked ~ #tabs-list #open-nav-label
display: none
// "show" close nav label when open, hide when closed
#close-nav-label
display: block
max-height: 0
overflow-y: hidden
background-color: $color-text
color: $color-label-bg-inactive
padding: 0px
transition: max-height $tab-transition-duration
cursor: pointer
text-transform: uppercase
font-size: 12px
line-height: 22px
letter-spacing: 1px
#nav-ctrl:checked ~ #tabs-list #close-nav-label
max-height: 36px
opacity: 1
padding: $menu-padding
// tabs list style
#tabs-list
position: relative
label.panel-label
padding: ($tab-padding / 2) 0
@for $i from 1 through $tab-count
#li-for-panel-#{$i}
max-height: 0
overflow-y: hidden
transition: max-height $tab-transition-duration
// tab controller triggering menu
@for $i from 1 through $tab-count
#panel-#{$i}-ctrl:checked ~ #tabs-list #li-for-panel-#{$i}
+active-menu-li-style
#panels
.container
width: 100%
section header
display: block
// big devices
@media all and (min-width: 768px)
// tab controller triggering tabs
@for $i from 1 through $tab-count
#panel-#{$i}-ctrl:checked ~ #tabs-list #li-for-panel-#{$i}
+active-tab-li-style
// tabs list style
ul#tabs-list
+tabs-list-style
// nav label
#open-nav-label,
#close-nav-label
display: none
// nav checkbox
#nav-ctrl
display: none
// real big devices
@media all and (min-width: 900px)
main
width: 70%
margin: 0 auto
/////////////////
// DEMO STYLES
/////////////////
// radios
.panel-radios
$width: 20px
$offset: $width / 2
$middle: ceil($tab-count/2)
//display: none
position: fixed
left: 50%
top: 10px
width: $width
opacity: 0.5
z-index: 99
// conditionally centering at top of screen (cant have wrapper...ugh math)
@for $i from 1 through $tab-count
@if $i < ceil($tab-count/2)
&:nth-child(#{$i})
transform: translateX((-1 * ($width * ($middle - $i))) - $offset)
@if $i == ceil($tab-count/2)
&:nth-child(#{$i})
transform: translateX(-1 * $offset)
@if $i > ceil($tab-count/2)
&:nth-child(#{$i})
transform: translateX($width * ($i - $middle) - $offset)
// menu checkbox
&:nth-child(#{$tab-count+1})
top: 30px
transform: translateX(-1 * ($width/2))
display: block
// body
body
background: $color-primary-light
color: $color-text
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, sans-serif
// banner
#introduction
width: 90%
margin: 0 auto
padding: $main-padding
color: white
h1
font-weight: 300
text-align: center
// content text formatting
main
h1
margin-top: 0
font-weight: 300
color: $color-primary-dark
p
line-height: 1.8
hr
margin: 12px 0
border-top: $tab-base-border-style
// demo styles
label.demo-label
background-color: $color-primary-dark
color: $color-label-bg-active
padding: 4px 8px
border-radius: 2px
cursor: pointer
display: inline-block
user-select: none
&:hover
background-color: $color-primary-light
#demo-child-toggle
display: none
&:checked ~ #demo-parent #demo-child
color: $color-primary-dark
font-weight: bold
text-transform: uppercase
#demo-parent
margin-top: 8px
// styling that code
code,
pre
color: $color-primary-dark
font-family: Inconsolata, 'Andale Mono', Andale, monowidth
background-color: $color-label-bg-inactive
border: $tab-base-border-style
border-radius: 2px
code
padding: 2px 6px
pre
padding: 12px
line-height: 1.6
strong
color: $color-text
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment