1

Using the ~ css selector it is possible to display some elements once a checkbox is clicked, which can be used to display and hide menus once elements are clicked, as this tutorial shows.

But the tilde selector doesn't select all the elements on the page, is there a selector that does?

I would like the .showtext and .hidetext <span> tags to be shown or hidden once a checkbox is clicked. Here is a js fiddle link.

HTML

<!-- The checkbox input & label partner -->
<input type="checkbox" id="menu-toggle">
<label for="menu-toggle">Menu 
<span class="showtext">show menu</span>
<span class="hidetext">hide menu</span>
</label>

<!-- The navigation we wish to toggle -->
<nav>
    <ul>
        <li><a href="">Home</a></li>
        <li><a href="">About</a></li>
        <li><a href="">Articles</a></li>
        <li><a href="">Colophon</a></li>
        <li><a href="">Contact</a></li>
    </ul>
</nav>

CSS

.hidetext { display: none }
input[type="checkbox"]:checked  .hidetext{
   display: block; 
}

/* Set the input position to absolute, send it off screen with zero opacity */
input[type="checkbox"] {
    left: -9999px;
    opacity: 0;
    position: absolute;
}

/* Minor visual styling to make the label more button-y */
label {
    border: 1px solid currentColor;
    border-radius: 4px;
    cursor: pointer;
    padding: 10px;
}

/* Set nav to absolute (avoids odd page rendering space pop-in) */
nav {
    opacity: 0;
    position: absolute;
    z-index: -2;
}

/* Show nav when checkbox is checked */
input[type="checkbox"]:checked ~ nav {
    opacity: 1;
    z-index: 1;
}
desbest
  • 4,746
  • 11
  • 51
  • 84
  • Does this answer your question? [What does the "~" (tilde/squiggle/twiddle) CSS selector mean?](https://stackoverflow.com/questions/10782054/what-does-the-tilde-squiggle-twiddle-css-selector-mean) – disinfor Dec 16 '20 at 20:32

4 Answers4

2

I think the issue here with the checkbox is that those spans are inside of the label of the checkbox you're checking. Here's a pure HTML/CSS method that will give you your desired outcome using the :after psuedo element with label in order to show and hide the "show menu" and "hide menu" text. I hope this is useful to you.

.hidetext { display: none }
input[type="checkbox"]:checked  .hidetext{
   display: block; 
}

/* Set the input position to absolute, send it off screen with zero opacity */
input[type="checkbox"] {
    left: -9999px;
    opacity: 0;
    position: absolute;
}

/* Minor visual styling to make the label more button-y */
label {
    border: 1px solid currentColor;
    border-radius: 4px;
    cursor: pointer;
    padding: 10px;
}

/* Set nav to absolute (avoids odd page rendering space pop-in) */
nav {
    opacity: 0;
    position: absolute;
    z-index: -2;
}

/* Show nav when checkbox is checked */
input[type="checkbox"]:checked ~ nav {
    opacity: 1;
    z-index: 1;
}

label:after {
  content: " show menu"
}

input[type="checkbox"]:checked ~ label:after {
  content: " hide menu";
}
<!-- The checkbox input & label partner -->

<input type="checkbox" id="menu-toggle">
<label for="menu-toggle">Menu</label>


<!-- The navigation we wish to toggle -->
<nav>
    <ul>
        <li><a href="">Home</a></li>
        <li><a href="">About</a></li>
        <li><a href="">Articles</a></li>
        <li><a href="">Colophon</a></li>
        <li><a href="">Contact</a></li>
    </ul>
</nav>
John
  • 5,132
  • 1
  • 6
  • 17
2

I'm able to accomplish the goal of toggling your "show menu" and "hide menu" text with this CSS:

input[type="checkbox"]:checked ~ label .hidetext{
   display: block; 
}

input[type="checkbox"]:checked ~ label .showtext {
  display: none;
}

Remember that CSS selectors are parsed right to left, so to understand this, read the first ruleset as:

.hidetext that is inside of a label that is a sibling of a :checked element that also has an attribute type whose value is checkbox that is also an input element.

And you'd read the second as:

.showtext that is inside of a label that is a sibling of a :checked element that also has an attribute type whose value is checkbox that is also an input element.

Pay close attention here as you read the selector: My selector targets an element inside of a sibling.

The tilde selector (AKA General sibling selector) targets any element after the element to the left. This means you can use ~ to target siblings and nieces/nephews (children of siblings). The only caveat is that they must come later in the markup, that's all.

So the input could control the display of anything on the page so long as it's the first thing in the <body>

Also keep in mind that you have special powers with the <input> and <label> elements:

If you use the id and the for attributes: <label for="myInput">Click It</label> <input id="myInput">, then clicking on the <label> will transfer focus to the <input>, which means clicking on the <label> will set the <input> to :checked

Which means,

  • <label> is a proxy for <input>
  • <label> can be placed anywhere on the page, so long as the for and id match.

Please keep in mind that while the label-checkbox hack is a fun way to store state in CSS and create conditional presentations, it is not necessarily good or sustainable.

In the long term, your codebase will benefit from a few lines of JavaScript that don't create a strong dependence on markup.

paceaux
  • 1,762
  • 1
  • 16
  • 24
  • Why does what I want to happen only work when `label` is added to the rule? Why does `input[type="checkbox"]:checked ~ label .hidetext{` work but `input[type="checkbox"]:checked ~ .hidetext{` not work? The HTML elements have the same class. – desbest Dec 17 '20 at 00:15
  • @desbest I explain that in my answer when I explain how to read the ruleset: "**.hidetext that is inside of a label** that is a sibling of a :checked element that also has an attribute type whose value is checkbox that is also an input element." Now read your selector:" `.hideText` **that is a sibling** to an element of a `:checked` element that also has an attribute type whose value is checkbox that is also an input element." Mine targets an element inside of a sibling. yours does not. – paceaux Dec 17 '20 at 01:09
  • @desbest The thing to understand here is that it's not just about the elements having the same class. Learn how to read those CSS selectors (Again, from right to left, like how I explain). The _rightmost_ part is the thing receiving the style. Everything to the left tells the browser how to find it. My CSS clarifies that `.hidetext` is found _inside_ of a `label`. Yours says that `.hidetext` is *not* inside of `anything, but right next to the input. – paceaux Dec 17 '20 at 01:17
1

As far as I know, you still cant do this specific thing in CSS, but I might be wrong.

My best advice is that you use JavaScript for this situation, because the element can be anywhere on a page, and I dont think there is a CSS selector that can navigate like that.

Using JavaScript for that would be much easier and much quicker :)

Just Alex
  • 457
  • 4
  • 6
1

The ~ in CSS represents "general sibling" so any element at the same level as the selector (same parent). Whereas a + selector would represent the "next" sibling.

To answer your question, No. There's not a CSS way to key off a change anywhere in the code. You would need to track the state of the checkbox with javascript and then add/remove styling.

However, you can accomplish what you need using only CSS, by merely putting the nav to hide/show inside the same parent as the checkbox element in your HTML and then using visual styling to manage how it looks.

In this case, put everything inside a <label> so that any clicks (like on the text) will toggle the input. Then you can hide the checkbox completely if you like.

Here's an example:

#container {
 position: absolute;
 top: 20px;
 bottom: 20px;
 left: 20px;
 width: 75%;
 border: 1px solid #ddd;
 overflow:hidden;
}

input {
  position: absolute;
  left: -9999px;
}
.button {
  display:inline-block;
  background: blue;
  padding: 5px 10px;
  color:#fff;
  border-radius: 5px;
  margin: 20px;
  cursor:pointer;
}
.button:after{
 content: "show nav";
}

ul {
  position: absolute;
  right: 0;
  top:0;
  bottom: 0;
  background: #333;
  color: #dadada;
  padding:0;
  margin:0;
  transition: transform 0.5s ease-in-out;
  transform: translateX(100%);
}

li {
  display:block;
  list-style: none;
  padding: 5px 30px;
}

input:checked ~ .button:after {
  content: "hide nav";
}

input:checked ~ ul {
  transform: translateX(0);
}
<div id="container">
  <label>
    <input type="checkbox">
    <span class="button"></span>
    <ul>
      <li>menu item</li>
      <li>menu item</li>
      <li>menu item</li>
      <li>menu item</li>
    <ul>
  </label>
</div>
Bryce Howitson
  • 7,339
  • 18
  • 40