I'd like to contribute an interactive SVG graph to Wikisource. There are quite strict requirements on the format, as indicated in the title:
style attributes and <style> elements, but besides that:foreignObject isles,<input> and CSS :checked tricks.I know about the :root:has(input[…]:checked) something {…} toggling technique, as seen in this HTML sample:
#main:has(#chkbox1) #triangle { display:none; }
#main:has(#chkbox1:checked) #triangle { display:block; }
#main:has(#chkbox2) #square { display:none; }
#main:has(#chkbox2:checked) #square { display:block; }
#main:has(#chkbox3) #circle { display:none; }
#main:has(#chkbox3:checked) #circle { display:block; }
<div id="main">
<div id="triangle">Triangle</div>
<div id="square">Square</div>
<div id="circle">Circle</div>
<input type="checkbox" id="chkbox1" checked="true">
<input type="checkbox" id="chkbox2" checked="true">
<input type="checkbox" id="chkbox3" checked="true">
</div>
(As you can see, this works without using JavaScript.) But it is just a HTML, that does not fit the requirements.
How can I simulate same functionality (hiding/showing some elements) without using any <input> tags and without using JavaScript?
I aim for a solution in which each <input> is replaced by some SVG node that simulates a checkbox using only allowed techniques, i.e., presumably CSS.
Here is an image of SVG generated on CodePen with the SVG I'd like to contribute, including the interactivity: https://codepen.io/schlebe/pen/gbbOrYK

Kaiido's ":target-based" answer is great, mainly because it preserves accessibility to a large extent (even keyboard!), and has neat ability to address any state using URL#hash so is indeed preferable.
Adding this alternative approach that is comparatively simpler, since introducing additional control here requires linear or (even constant, see below), not exponential additions.
:active and spring-loaded animationIt exploits super-short paused animations and :active trigger that releases the animation with animation-play-state: running. Animation runs and the state is kept thanks to the animation-fill-mode: both. For returning back to the original state, animation: none is used, what after release basically resets the animation timeline back to start, when the original animation is re-applied.
As extra challenge, this sample does not even use :has(), so it's CSS is pretty old-school. Also for simplicity, the "Trigger" elements are just on/of (filled/empty) rectangles superimposed over each other, not mutually toggled.
<embed height="150" src='data:image/svg+xml,<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 50"
fill="none"
pointer-events="all"
><style>
/* Using scale-to-zero for hiding, see notes */
@keyframes hide { to { transform: scale(0) } }
/* "Spring-loaded" animation */
path, rect { animation: .1ms hide both paused }
/* "Hide triggers" above "Show triggers" hide themselves and path */
rect[fill="red"]:active,
rect[fill="red"]:active ~ path[stroke="red"] ,
rect[fill="green"]:active,
rect[fill="green"]:active ~ path[stroke="green"] {
animation-play-state: running
}
/* "Show trigger" underneath following "Hide trigger" show it and path */
rect[stroke="red"]:active + rect,
rect[stroke="red"]:active ~ path[stroke="red"] ,
rect[stroke="green"]:active + rect,
rect[stroke="green"]:active ~ path[stroke="green"] {
animation: none
}
</style>
<!-- Show/Hide "Triggers" -->
<rect stroke="red" width="30" height="20" x="63" y="3" />
<rect fill="red" width="30" height="20" x="63" y="3" />
<rect stroke="green" width="30" height="20" x="63" y="27" />
<rect fill="green" width="30" height="20" x="63" y="27" />
<!-- "Graph" -->
<path stroke="red" d="M0 0 l 20 10 40 40" />
<path stroke="green" d="M0 50 l 20 -30 40 -20" />
</svg>'>
As indicated in the prologue, this approach is not keyboard-accessible. It is possible to add tabindex="0", or fuse it with <a href="#">nchors, but sadly, keyboard interaction (Enter/Spacebar) does not trigger the :active state the way pointer does. (I guess it should not be this way, but the reality is like it is.)
And as Kaiido suggested, altering of the source structure by intermixing controls among the target "graph" elements unlocks a major simplification: effectively making the CSS static, i.e., not needing alterations when introducing new interactive content.
Resulting sample with few more shenanigans making the SVG code itself as terse as possible, and without need for repeating anything in the CSS, this time as a regular SO HTML snippet:
@keyframes hide {
to { transform: scale(0) }
}
path,
rect {
animation: .1ms hide both paused;
stroke: currentcolor;
}
rect + rect {
fill: currentcolor;
}
rect + rect:active,
rect + rect:active + path {
animation-play-state: running;
}
rect:active + rect,
rect:active + rect + path {
animation: none;
}
svg {
fill: none;
pointer-events: all;
}
rect {
width: 30px;
--stroke: 2px;
stroke-width: var(--stroke);
height: calc((100% / var(--count)) - 2 * (var(--stroke)));
x: 63px;
y: calc(100% / var(--count) * var(--index) + var(--stroke));
}
/*
Just emulating "sibling count" and "sibling index" for [1..4]
Cannot wait to actually have it in the CSS
*/
g { --count: 1; --index: 0; }
g:nth-child(2) { --index: 1; }
g:nth-child(3) { --index: 2; }
g:nth-child(4) { --index: 3; }
g:first-child:nth-last-child(2){ &, & ~ g { --count: 2; } }
g:first-child:nth-last-child(3){ &, & ~ g { --count: 3; } }
g:first-child:nth-last-child(4){ &, & ~ g { --count: 4; } }
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 50" height="150">
<g>
<g color="green"><rect /><rect />
<path d="M0 50 l 20 -30 40 -20" />
</g>
<g color="gold"><rect /><rect />
<path d="M0 25 l 20 10 40 -10" />
</g>
<g color="red"><rect /><rect />
<path d="M0 0 l 20 10 40 40" />
</g>
</g>
<!--
Style moved outside, for nicer SO snippet.
-->
</svg>
(There is just unrelated "sibling count" emulation for making even the placement of "buttons" automatic, that uses nesting for brevity. Besides that the SVG demands on the CSS capabilities remains pretty basic.)
For implementing declarative initial on-off state, we can use some data-attribute as the directive (data-initial="off" here) and flip the direction and button effects. For brevity, custom properties with fallback defaults come in handy:
@keyframes hide {
to { transform: scale(0); }
}
rect + rect,
rect + rect + path {
animation: .1ms hide both paused var(--dir, normal);
}
rect:active + rect,
rect:active + rect + path {
animation-play-state: running;
animation-name: var(--bottom-act, none);
}
rect + rect:active,
rect + rect:active + path {
animation-play-state: running;
animation-name: var(--top-act, hide);
}
[data-initial="off"] {
--dir: reverse;
--top-act: none;
--bottom-act: hide;
}
path,
rect {
stroke: currentcolor;
}
rect + rect {
fill: currentcolor;
}
svg {
fill: none;
pointer-events: all;
}
rect {
width: 30px;
--stroke: 2px;
stroke-width: var(--stroke);
height: calc((100% / var(--count)) - 2 * (var(--stroke)));
x: 63px;
y: calc(100% / var(--count) * var(--index) + var(--stroke));
}
/*
Just emulating "sibling count" and "sibling index" for [1..4]
Cannot wait to actually have it in the CSS
*/
g { --count: 1; --index: 0; }
g:nth-child(2) { --index: 1; }
g:nth-child(3) { --index: 2; }
g:nth-child(4) { --index: 3; }
g:first-child:nth-last-child(2){ &, & ~ g { --count: 2; } }
g:first-child:nth-last-child(3){ &, & ~ g { --count: 3; } }
g:first-child:nth-last-child(4){ &, & ~ g { --count: 4; } }
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 50" height="150">
<g>
<g color="green">
<rect /><rect />
<path d="M0 50 l 20 -30 40 -20" />
</g>
<g color="gold">
<rect /><rect />
<path d="M0 25 l 20 10 40 -10" />
</g>
<g color="red" data-initial="off">
<rect /><rect />
<path d="M0 0 l 20 10 40 40" />
</g>
</g>
<!--
Style moved outside, for nicer SO snippet.
-->
</svg>
transform: scale(0) for hiding and not display: none?
display is not animatable yet.:active state, is Chrome-button. Specs are somewhat lax about this topic. There are some quite stalled discussion in the CSS working group's GH:
:active specification more explicit as to which interactions cause its application #4787 where it currently stopped at "not a spec problem, but implementers'", andIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With