0

I'm creating a simply show more solution for a page where there are n text elements that need toggling between show and hide. n is dynamic and not fixed.

For the purposes of this question, I'm exploring non-JS, CSS-only solutions.

It's possible to achieve a show and hide toggle functionality for a single element via CSS (solution below). But how does one extend it to n elements? Would be great to get an illustrative, working answer.


How I would do it in a single text element case:

#textarea {
    /* hide by default: */
    display: none;
}

/* when the checkbox is checked, show the neighbouring #textarea element: */
#textAreaToggle:checked + #textarea {
    display: block;
}

/* position the checkbox off-screen: */
input[type="checkbox"] {
    position: absolute;
    left: -1000px;
}

/* Aesthetics only, adjust to taste: */
label {
    display: block;
}

/* when the checkbox is unchecked (its default state) show the text
   'Show ' in the label element: */
#textAreaToggle + #textarea + label::before {
    content: 'Show ';
}

/* when the checkbox is checked 'Hide ' in the label element; the
   general-sibling combinator '~' is required for a bug in Chrome: */
#textAreaToggle:checked ~ #textarea + label::before {
    content: 'Hide ';
}
<input id="textAreaToggle" type="checkbox" /><p id="textarea">This is hidden textarea, that needs to be shown</p><label for="textAreaToggle">textarea</label>

This single-case solution is based on this answer, and it's tried and tested. You can run the code snippet to see for yourself.

But I'm struggling to generalize it for n text elements on a single page (in a CSS-only setting), thus this question.

Hassan Baig
  • 15,055
  • 27
  • 102
  • 205

4 Answers4

4

Why not simply use the details element?

[open] summary {
  position: absolute;
  bottom: -1.5em;
  left: 0;
}

summary::before {
  content: "...More";
}

[open] summary::before {
  content: "Less";
}

details {
  display: inline;
}

.more-text {
  position: relative;
  margin-bottom: 2em;
}
<div class="more-text">
  Lorem ipsum dolor, sit amet consectetur adipisicing elit. Impedit laborum nesciunt dolorem deleniti non magnam natus iure nobis quaerat amet commodi aspernatur,
  <details>
    <summary></summary>
    ad, maiores possimus fugiat ipsum assumenda cum? Voluptas.
  </details>
</div>
<div class="more-text">
  Lorem ipsum dolor, sit amet consectetur adipisicing elit. Impedit laborum nesciunt dolorem deleniti non magnam natus iure nobis quaerat amet commodi aspernatur,
  <details>
    <summary></summary>
    ad, maiores possimus fugiat ipsum assumenda cum? Voluptas.
  </details>
</div>
<div class="more-text">
  Lorem ipsum dolor, sit amet consectetur adipisicing elit. Impedit laborum nesciunt dolorem deleniti non magnam natus iure nobis quaerat amet commodi aspernatur,
  <details>
    <summary></summary>
    ad, maiores possimus fugiat ipsum assumenda cum? Voluptas.
  </details>
</div>
<div class="more-text">
  Lorem ipsum dolor, sit amet consectetur adipisicing elit. Impedit laborum nesciunt dolorem deleniti non magnam natus iure nobis quaerat amet commodi aspernatur,
  <details>
    <summary></summary>
    ad, maiores possimus fugiat ipsum assumenda cum? Voluptas.
  </details>
</div>

If you need legacy browser support, here's a very small polyfill that comes without any dependencies:

https://github.com/rstacruz/details-polyfill

connexo
  • 53,704
  • 14
  • 91
  • 128
1

You want different toggles for different text. You can give them unique id's to work with and then aggregate all of them in the ~ selector. So

For #fortext(1) checked, we display #textarea(1)

For #fortext(2) checked, we display #textarea(2)

For #fortext(n) checked, we display #textarea(n)

p {
  /* hide by default: */
  display: none;
}

#fortext1:checked~#textarea1,
#fortext2:checked~#textarea2,
#fortext3:checked~#textarea3 {
  display: block;
}
<input id="fortext1" type="checkbox" /><br>
<p id="textarea1">textarea1</p>
<input id="fortext2" type="checkbox" /><br>
<p id="textarea2">textarea2</p>
<input id="fortext3" type="checkbox" /><br>
<p id="textarea3">textarea3</p>

The general sibling selector selects the next sibling of the selector. Like this

selector ~ siblings to affect {
   code goes here
}

You can understand this by the snippet below

.selector~p { /* this means that after the class selector, every p sibling will be colored red */ 
  color: red
}
<p class="selector">Main Selector</p>
<p>P tag</p>
<span>span</span>
<span>span</span>
<span>span</span>
<p>P tag</p>
<p>P tag</p>
<span>span</span>
<p>P tag</p>
weegee
  • 3,256
  • 2
  • 18
  • 32
  • 1
    Could you expand more about the usage of `~` in your answer. Clearly that's a trick I seem to have missed. – Hassan Baig Jul 07 '19 at 18:54
  • Ah I see the usage. Here's the thing: I need all `n` textareas to be **separately** toggle-able, independent of each other. Currently as per your suggested solution, they're all controlled by the same toggle switch, which defeats the purpose. – Hassan Baig Jul 07 '19 at 19:25
  • Right, so the only way is to duplicate the CSS for all the `n` text areas eh? I'll take it for a spin. – Hassan Baig Jul 07 '19 at 19:43
  • @HassanBaig you want **specific, toggle-able** checkboxes that display some text on a check. Yes, you meant **specific** *this* is **specific**. There's no general solution to it in CSS. But it does in javascript. – weegee Jul 07 '19 at 19:46
  • Well, just my opinion, but any time I can accomplish functionality in good old CSS, I prefer that over JS :-) I'll take your suggestion for a spin and get back to you. – Hassan Baig Jul 07 '19 at 19:58
  • Follow up question: would this solution, the way it stands now, really need `~`? – Hassan Baig Jul 08 '19 at 06:44
  • @HassanBaig yes how else would you select all the other siblings the way the selector does? – weegee Jul 08 '19 at 07:36
  • 1
    Selected this answer because this is more backward compatible than techniques utilizing `summary` and `details`. Having said that, the simplicity of those elements to achieve the desired result can't be overstated. – Hassan Baig Jul 08 '19 at 11:50
  • @HassanBaig True because [details element is not supported by edge and IE](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details#Browser_compatibility) – weegee Jul 08 '19 at 11:52
  • @connexo requiring the OP to install a library for a solution that is simple is never ideal. – weegee Jul 08 '19 at 19:04
  • @weegee I'd always prefer a standard-based solution plus a polyfill over any custom implementation. – connexo Jul 08 '19 at 19:09
1

Here is a generic idea where the only requirement is to have a different wrapper per text.

I considered CSS grid to be able to put the label before in the DOM then change its position visually. I also made the label and the input on the same row/column having the input on the top to trigger the click. Then I simply create an overflow for the input so we don't see it.

.box {
  display:grid;
  overflow:hidden;
  margin:10px;
}

.box span,input[type="checkbox"] {
  grid-row:1;
  grid-column:1;
  width:100%;
  height:100%;
}
input[type="checkbox"] {
  z-index:2;
  width:300%; /*big value to create the overflow*/
}
/* show/hide the text*/
p {
  display: none;
}
input:checked ~ p {
  display: block;
}
/**/
span {
  color:red;
}

/* Change label text*/
input + span::before {
  content: 'Show ';
}
input:checked + span::before {
  content: 'Hide ';
}
/**/
<div class="box">
  <input type="checkbox" />
  <span>textarea</span>
  <p >This is hidden textarea, that needs to be shown</p>
</div>
<div class="box">
  <input type="checkbox" />
  <span>textarea</span>
  <p >This is hidden textarea, that needs to be shown</p>
</div>
<div class="box">
  <input type="checkbox" />
  <span>textarea</span>
  <p >This is hidden textarea, that needs to be shown</p>
</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • Could you expand on "the only requirement is to have a different wrapper per text"? I'd like to see how you're using that in your sample code, so would be good to add a bit more clarification :-) – Hassan Baig Jul 09 '19 at 07:13
  • @HassanBaig it's the `box` element in my code. It's the wrapper around the text and the input. Since I am using Grid and order I need to a least have a wrapper per block – Temani Afif Jul 09 '19 at 08:19
  • I'll tell you what I'm confused about. Check out the answer I accepted (the one above yours). We had to resort to using as many CSS classes/IDs as there were text elements on the page. Your solution seems to have side-stepped that, making the code simpler in essence. Is that because of this: `input:checked ~ p { display: block; }`? – Hassan Baig Jul 09 '19 at 08:23
  • 1
    @HassanBaig this is why I am using a wrapper, to avoid ussing ID/Classes and to have something generic and scalable. the wrapper will isolate each input/text making the work easy. By doing `input:checked ~ p { display: block; }` I am sure to target only the needed `p` and not other `p` element. – Temani Afif Jul 09 '19 at 08:27
  • That indeed makes it more generalizable. I'll take it for a spin and get back to you. – Hassan Baig Jul 09 '19 at 08:29
  • Follow up question: could you explain further why `grid` is important to make it work? When I remove it, the `show` label becomes unresponsive. On the surface, it just looked like a stylistic choice, but I guess more is going on under the hood. – Hassan Baig Jul 09 '19 at 10:15
  • 1
    @HassanBaig it's important because it allow me to place both label and input on the same grid area and both will cover it fully (width:100% height:100%) then by making the input on the top (z-index:2) it will catch the click and it's like you are clicking on the label but in reality it's the input. And to make sure the input will not be shown, I used a big width for it so it's overflowing. Well, Grid is the most important part in this trick. – Temani Afif Jul 09 '19 at 10:21
  • The only reason I asked is because I want some backward compatibility with browser versions which may not support css grid (e.g. old opera versions). But in any case, it's a great trick. – Hassan Baig Jul 09 '19 at 10:23
1

In order to get the collapse effect you can consider using the html elements: details and summary.

You can add your own CSS as you wish, but you don't have to write the toggle part since it is built in.

Generic example:

<details>
    <summary>Read More</summary>
    More of your content....
</details>

Read more on MDN

A. Meshu
  • 4,053
  • 2
  • 20
  • 34