MonkRocker MonkRocker - 1 year ago 51
CSS Question

How to store "state" in CSS only?

I am working on solving a conundrum, and I have it mostly figured out except for one big part:

Imagine if you will 5 divs. A Header and 4 Sections (a - d).

Each section has an open/close toggle I did using the CSS Checkbox Hack to toggle the value of open/close. When open, a section displays content inside of it and the button toggles to 'close' Clicking that hides the content and toggles the button back to 'open'. All that works just fine.

As part of the requirements: the Header should be constantly displaying which sections are open, in a grammatically correct form:

"all sections are closed"

"section A is open"

"sections A and C are open" <-- the plural is important here.

I am a javascript guy mostly and this would be a trivial problem in javascript. Problem is? Has to be pure CSS. Why? Because that's the requirement. I suspect I am actually instead being tested on whether or not I can find the resources necessary to solve the problem. I say this because it's a javascript development position - so odd they would test me on pure CSS, AND it's a 100% remote position. When I used to work remote, 80% of the job was googling or SO, so here I am. ;)

I'm assuming that the solution involves abusing the content: property along with a css counter or even multiple ones, but so far, I am at a loss as to how one might do it.

Any markup is allowed, and any CSS, but NO javascript. Also has to be cross-browser compatible, but I will be satisfied with it working on ONE to start with.

Can someone point me in the right direction?


Edit: Code of what I've got so far:

input[type='checkbox'] {
visibility: hidden;
label:after {
color: blue;
text-decoration: underline;
display: inline;
position: absolute;
right: 10px;
content: 'open';
#sectionA:checked ~ label:after {
content: 'close';
.section {
border: 1px solid black;
border-radius: 4px;
margin: 1% auto;
position: relative;
line-height: 35px;
padding-left: 5px;
width: 100%;
.header {
background-color: #ddd;
.sectionName {
color: #ffa500;
font-style: italic;
display: inline;
.sectionContent {
max-height: 0;
display: none;
transition: all .4s;
#sectionA:checked + .sectionContent {
max-height: 30px;
display: block;
.clearfix:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
.clearfix {
display: inline-block;
#siblings {
color: #333;
h2 ~ div {
color: red;

<div class="section header">
<span id="headerMessage">all sections are closed</span>
<div class="section closed clearfix">
section <span class="sectionName">a</span>
<label for="sectionA" id="labelA"></label>
<input type=checkbox id="sectionA" />
<div class="sectionContent">section <span class="sectionName">a</span> content</div>
<div class="section closed clearfix">
section <span class="sectionName">b</span>
<label for="sectionB"></label>
<input type=checkbox id="sectionB" />
<div class="sectionContent">section <span class="sectionName">b</span> content</div>
<div class="section closed">
section <span class="sectionName">c</span>
<div class="section closed">
section <span class="sectionName">d</span>

I haven't added the classes for sections b-d, hence them not working yet, but they will act the same as section a.

Answer Source

It is possible, but ONLY if the list of opened tabs is at the very bottom of the page (so below the tabs). If it is, it is a simple matter of dealing with all situations one by one. I've done so in the following code:

/* Hide everything to start with */
.t {
  display: none;

/* Show the correct tab when needed */
#a:checked ~ #tab1,
#b:checked ~ #tab2,
#c:checked ~ #tab3,
#d:checked ~ #tab4 {
  display: block;

/* When nothing is checked, show #T0, otherwise, show #T1 */
#a:not(:checked) + #b:not(:checked) + #c:not(:checked) + #d:not(:checked) ~ #count #T0,
:checked ~ #count #T1 {
   display: inline;

/* Check if the message should be plural or singular */
:checked ~ :checked ~ #count .plural {
  display: inline;
:checked ~ :checked ~ #count .singular {
  display: none;

#a:checked ~ #count .taba,
#b:checked ~ #count .tabb,
#c:checked ~ #count .tabc,
#d:checked ~ #count .tabd {
  display: inline;

/* Show the required tabs,
   with proper comma and 'and' placement
/* if ? and d are open, we need 'and' */
:checked ~ #d:checked ~ #count .andcd,
/* if ? and c are open but d is not, we need 'and' */
:checked ~ #c:checked ~ #d:not(:checked) ~ #count .andbc,
/* if a and b are open but c and d are not, we need 'and' */
#a:checked ~ #b:checked ~ #c:not(:checked) ~ #d:not(:checked) ~ #count .andab,
/* if a, b and ? are open, we need a comma */
#a:checked ~ #b:checked ~ :checked ~ #count .comab,
/* if ?, c and d are open, we need a comma */
:checked ~ #c:checked ~ #d:checked ~ #count .combc {
  display: inline;

/* make the counter appear as if it is at the very top of the page,
   even though it is not.
/* Make room at the top, and force absolute positions to be relative
   to #container*/
#container {
  position: relative;
  padding-top: 2em;
/* move tab-counter to the top */
#count {
  top: 0;
  <div id="container">
    <input id="a" type="checkbox"/>
    <input id="b" type="checkbox"/>
    <input id="c" type="checkbox"/>
    <input id="d" type="checkbox"/>
    <div id="tab1" class="tab t1"><label for="a">Tab1</label></div>
    <div id="tab2" class="tab t2"><label for="a">Tab2</label></div>
    <div id="tab3" class="tab t3"><label for="a">Tab3</label></div>
    <div id="tab4" class="tab t4"><label for="a">Tab4</label></div>
    <div id="count">
      <span id="T0">None of the tabs are open</span>
      <span id="T1">
        Tab<span class="plural">s</span>
        <!--tab listing-->
        <span class="t taba">a</span><!--
        --><span class="t comab">, </span><!--
        --><span class="t andab"> and </span><!--
        --><span class="t tabb">b</span><!--
        --><span class="t combc">, </span><!--
        --><span class="t andbc"> and </span><!--
        --><span class="t tabc">c</span><!--
        --><span class="t andcd"> and </span><!--
        --><span class="t tabd">d</span><!--
        <span class="singular">is</span>
        <span class="plural">are</span>

I've tried to be as efficient as possible with the correct grammar, by simply toggling commas and and spans whenever needed, and also being as efficient as possible with it. I've tested all 16 scenarios, and this does work correctly for each of them.

As far as code-efficiency goes, I think the amount of conditions for the bottom display:inline style is linear with the amount of tabs, and the amount of interpunction-spans is also linear with the amount of tabs. That means this is inefficient code (compared to scripts), but still not exponential like I initially thought.

Edit: In reaction to you posting your JSFiddle, that is impossible. You simply can't access parent elements or previous-siblings in CSS. You'll have to put your header at the very bottom of your document. You can mess with absolute positioning if you want though. See my updated code for more.

PS: this almost feels like a code-golf question if you didn't have that reason to want to do this, and it would have been a darn good challenge on too.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download