Skip to content

Instantly share code, notes, and snippets.

@azriel91
Last active December 26, 2025 06:19
Show Gist options
  • Select an option

  • Save azriel91/95d11b8d880a4d5fdc95dc635575b24a to your computer and use it in GitHub Desktop.

Select an option

Save azriel91/95d11b8d880a4d5fdc95dc635575b24a to your computer and use it in GitHub Desktop.
SVG with css for interactivity
Display the source blob
Display the rendered blob
Raw
<!--?xml version="1.0" encoding="UTF-8" standalone="no"?-->
<svg
id="clear"
class="group/root"
viewBox="0 0 400 350"
width="400"
height="350"
xmlns="http://www.w3.org/2000/svg"
>
<style>
text {
font-family: Arial, sans-serif;
}
*, ::after, ::before, ::backdrop, ::file-selector-button {
box-sizing: border-box;
margin: 0;
padding: 0;
border: 0 solid;
}
::before, ::after {
--en-content: '';
}
html, :host {
line-height: 1.5;
-webkit-text-size-adjust: 100%;
tab-size: 4;
font-family: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
font-feature-settings: normal;
font-variation-settings: normal;
-webkit-tap-highlight-color: transparent;
}
hr {
height: 0;
color: inherit;
border-top-width: 1px;
}
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
h1, h2, h3, h4, h5, h6 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
-webkit-text-decoration: inherit;
text-decoration: inherit;
}
b, strong {
font-weight: bolder;
}
code, kbd, samp, pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-feature-settings: normal;
font-variation-settings: normal;
font-size: 1em;
}
small {
font-size: 80%;
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
table {
text-indent: 0;
border-color: inherit;
border-collapse: collapse;
}
:-moz-focusring {
outline: auto;
}
progress {
vertical-align: baseline;
}
summary {
display: list-item;
}
ol, ul, menu {
list-style: none;
}
img, svg, video, canvas, audio, iframe, embed, object {
display: block;
vertical-align: middle;
}
img, video {
max-width: 100%;
height: auto;
}
button, input, select, optgroup, textarea, ::file-selector-button {
font: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
color: inherit;
border-radius: 0;
background-color: transparent;
opacity: 1;
}
:where(select:is([multiple], [size])) optgroup {
font-weight: bolder;
}
:where(select:is([multiple], [size])) optgroup option {
padding-inline-start: 20px;
}
::file-selector-button {
margin-inline-end: 4px;
}
::placeholder {
opacity: 1;
}
@supports (not (-webkit-appearance: -apple-pay-button)) /* Not Safari */ or (contain-intrinsic-size: 1px) /* Safari 17+ */ {
::placeholder {
color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
textarea {
resize: vertical;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-date-and-time-value {
min-height: 1lh;
text-align: inherit;
}
::-webkit-datetime-edit {
display: inline-flex;
}
::-webkit-datetime-edit-fields-wrapper {
padding: 0;
}
::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {
padding-block: 0;
}
:-moz-ui-invalid {
box-shadow: none;
}
button, input:where([type='button'], [type='reset'], [type='submit']), ::file-selector-button {
appearance: button;
}
::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
height: auto;
}
[hidden]:where(:not([hidden='until-found'])) {
display: none !important;
}
*, ::before, ::after, ::backdrop, ::-webkit-backdrop {
--en-border-spacing-x: 0;
--en-border-spacing-y: 0;
--en-translate-x: 0;
--en-translate-y: 0;
--en-translate-z: 0;
--en-rotate-x: 0;
--en-rotate-y: 0;
--en-rotate-z: 0;
--en-skew-x: 0;
--en-skew-y: 0;
--en-scale-x: 1;
--en-scale-y: 1;
--en-scale-z: 1;
--en-pan-x: ;
--en-pan-y: ;
--en-pinch-zoom: ;
--en-scroll-snap-strictness: proximity;
--en-ordinal: ;
--en-slashed-zero: ;
--en-numeric-figure: ;
--en-numeric-spacing: ;
--en-numeric-fraction: ;
--en-ring-inset: ;
--en-ring-offset-width: 0px;
--en-ring-offset-color: #fff;
--en-ring-color: currentColor;
--en-ring-offset-shadow: 0 0 #0000;
--en-ring-shadow: 0 0 #0000;
--en-shadow: 0 0 #0000;
--en-shadow-colored: 0 0 #0000;
--en-blur: ;
--en-brightness: ;
--en-contrast: ;
--en-grayscale: ;
--en-hue-rotate: ;
--en-invert: ;
--en-saturate: ;
--en-sepia: ;
--en-drop-shadow: ;
--en-backdrop-blur: ;
--en-backdrop-brightness: ;
--en-backdrop-contrast: ;
--en-backdrop-grayscale: ;
--en-backdrop-hue-rotate: ;
--en-backdrop-invert: ;
--en-backdrop-opacity: ;
--en-backdrop-saturate: ;
--en-backdrop-sepia: ;
}
.invisible {
visibility: hidden;
}
.rounded-lg {
border-radius: 0.5rem;
}
.fill-blue-200 {
fill: oklch(88.2% .059 254.128);
}
.fill-neutral-950 {
fill: oklch(14.5% 0 0);
}
.fill-slate-200 {
fill: oklch(92.9% .013 255.508);
}
.fill-slate-300 {
fill: oklch(86.9% .022 252.894);
}
.fill-slate-400 {
fill: oklch(70.4% .04 256.788);
}
.stroke-neutral-950 {
stroke: oklch(14.5% 0 0);
}
.stroke-slate-500 {
stroke: oklch(55.4% .046 257.417);
}
.stroke-slate-900 {
stroke: oklch(20.8% .042 265.755);
}
.stroke-\[2px\] {
stroke-width: 2px;
}
.font-normal {
font-weight: 400;
}
.hover\:stroke-green-500:hover {
stroke: oklch(72.3% .219 149.579);
}
.hover\:fill-green-200:hover {
fill: oklch(92.5% .084 155.995);
}
.focus\:stroke-green-700:focus {
stroke: oklch(52.7% .154 150.069);
}
.focus\:fill-red-400:focus {
fill: oklch(70.4% .191 22.216);
}
.focus\:fill-green-400:focus {
fill: oklch(79.2% .209 151.711);
}
.focus\:fill-green-300:focus {
fill: oklch(87.1% .15 154.449);
}
.focus\:fill-blue-400:focus {
fill: oklch(70.7% .165 254.624);
}
.focus\:border-red-500:focus {
border-color: oklch(63.7% .237 25.331);
}
.focus\:border-green-500:focus {
border-color: oklch(72.3% .219 149.579);
}
.focus\:border-blue-500:focus {
border-color: oklch(62.3% .214 259.815);
}
.focus\:border-2:focus {
border-width: 2px;
}
.peer\/process_selector:has(#proc-one:visible) ~ .peer-\[\:has\(\#proc-one\:visible\)\]\/process_selector\:visible {
visibility: visible;
}
.group\/root:has(#proc-two:target) .group-\[\:has\(\#proc-two\:target\)\]\/root\:visible {
visibility: visible;
}
.group\/root:has(#proc-two:target) .group-\[\:has\(\#proc-two\:target\)\]\/root\:fill-blue-500 {
fill: oklch(62.3% .214 259.815);
}
.group\/root:has(#proc-three:target) .group-\[\:has\(\#proc-three\:target\)\]\/root\:visible {
visibility: visible;
}
.group\/root:has(#proc-three:target) .group-\[\:has\(\#proc-three\:target\)\]\/root\:fill-green-500 {
fill: oklch(72.3% .219 149.579);
}
.group\/root:has(#proc-one:target) .group-\[\:has\(\#proc-one\:target\)\]\/root\:visible {
visibility: visible;
}
.group\/root:has(#proc-one:target) .group-\[\:has\(\#proc-one\:target\)\]\/root\:fill-red-500 {
fill: oklch(63.7% .237 25.331);
}
.group:focus-within .group-\[\:focus-within\]\:visible {
visibility: visible;
}
.group:first-child:focus-within .group-\[\:first-child\:focus-within\]\:visible {
visibility: visible;
}
.group#process selector box:focus-within .group-\[\#process_selector_box\:focus-within\]\:visible {
visibility: visible;
}
:target {
visibility: visible;
}
.peer\/proc_3_step_3:focus ~ .peer-focus\/proc_3_step_3\:fill-green-600 {
fill: oklch(62.7% .194 149.214);
}
.peer\/proc_3_step_2:focus ~ .peer-focus\/proc_3_step_2\:fill-green-500 {
fill: oklch(72.3% .219 149.579);
}
.peer\/proc_3_step_1:focus ~ .peer-focus\/proc_3_step_1\:fill-green-400 {
fill: oklch(79.2% .209 151.711);
}
.peer\/proc_2_step_2:focus ~ .peer-focus\/proc_2_step_2\:fill-blue-500 {
fill: oklch(62.3% .214 259.815);
}
.peer\/proc_2_step_1:focus ~ .peer-focus\/proc_2_step_1\:fill-blue-400 {
fill: oklch(70.7% .165 254.624);
}
.peer\/proc_1_step_2:focus ~ .peer-focus\/proc_1_step_2\:fill-red-500 {
fill: oklch(63.7% .237 25.331);
}
.peer\/proc_1_step_1:focus ~ .peer-focus\/proc_1_step_1\:fill-red-400 {
fill: oklch(70.4% .191 22.216);
}
</style>
<text x="20" y="20" font-size="18" text-anchor="left" class="fill-neutral-950">Processes:</text>
<!-- Main box -->
<rect
x="10"
y="30"
width="200"
height="300"
rx="4"
ry="4"
class="
stroke-[2px]
fill-slate-200
stroke-slate-500
"
/>
<!--
List of process steps
These are placed before the list of processes so that they are rendered underneath the processes.
`peer-[:has(#proc-one:visible)]/process_selector:visible` class doesn't work on `set` / animated values, because the animated class value is not readable by CSS.
-->
<!-- proc 1 steps -->
<g
transform="translate(45 100)"
tabindex="3"
class="
invisible
fill-slate-400
peer/proc_1_step_1
focus:border-2
focus:fill-red-400
focus:border-red-500
group-[:has(#proc-one:target)]/root:visible
"
>
<rect width="120" height="40" rx="4" ry="4" />
<text
x="10"
y="25"
font-size="18"
text-anchor="left"
class="fill-neutral-950"
>P1: Step 1</text>
</g>
<g
transform="translate(45 150)"
tabindex="3"
class="
invisible
fill-slate-400
peer/proc_1_step_2
focus:border-2
focus:fill-red-400
focus:border-red-500
group-[:has(#proc-one:target)]/root:visible
"
>
<rect width="120" height="40" rx="4" ry="4" />
<text
x="10"
y="25"
font-size="18"
text-anchor="left"
class="fill-neutral-950"
>P1: Step 2</text>
</g>
<!-- proc 2 steps -->
<g
transform="translate(45 100)"
tabindex="3"
class="
invisible
fill-slate-400
peer/proc_2_step_1
focus:border-2
focus:fill-blue-400
focus:border-blue-500
group-[:has(#proc-two:target)]/root:visible
"
>
<rect width="120" height="40" rx="4" ry="4" />
<text
x="10"
y="25"
font-size="18"
text-anchor="left"
class="fill-neutral-950"
>P2: Step 1</text>
</g>
<g
transform="translate(45 150)"
tabindex="3"
class="
invisible
fill-slate-400
peer/proc_2_step_2
focus:border-2
focus:fill-blue-400
focus:border-blue-500
group-[:has(#proc-two:target)]/root:visible
"
>
<rect width="120" height="40" rx="4" ry="4" />
<text
x="10"
y="25"
font-size="18"
text-anchor="left"
class="fill-neutral-950"
>P2: Step 2</text>
</g>
<!-- proc 3 steps -->
<g
transform="translate(45 100)"
tabindex="3"
class="
invisible
fill-slate-400
peer/proc_3_step_1
focus:border-2
focus:fill-green-400
focus:border-green-500
group-[:has(#proc-three:target)]/root:visible
"
>
<rect width="120" height="40" rx="4" ry="4" />
<text
x="10"
y="25"
font-size="18"
text-anchor="left"
class="fill-neutral-950"
>P3: Step 1</text>
</g>
<g
transform="translate(45 150)"
tabindex="3"
class="
invisible
fill-slate-400
peer/proc_3_step_2
focus:border-2
focus:fill-green-400
focus:border-green-500
group-[:has(#proc-three:target)]/root:visible
"
>
<rect width="120" height="40" rx="4" ry="4" />
<text
x="10"
y="25"
font-size="18"
text-anchor="left"
class="fill-neutral-950"
>P3: Step 2</text>
</g>
<g
transform="translate(45 200)"
tabindex="3"
class="
invisible
fill-slate-400
peer/proc_3_step_3
focus:border-2
focus:fill-green-400
focus:border-green-500
group-[:has(#proc-three:target)]/root:visible
"
>
<rect width="120" height="40" rx="4" ry="4" />
<text
x="10"
y="25"
font-size="18"
text-anchor="left"
class="fill-neutral-950"
>P3: Step 3</text>
</g>
<!-- Process input box -->
<g
transform="translate(35 50)"
class="
stroke-[2px]
fill-slate-300
stroke-slate-500
rounded-lg
group
peer/process_selector
"
>
<g
tabindex="1"
id="process_input_selector"
class="
stroke-[2px]
fill-slate-300
stroke-slate-500
hover:stroke-green-500
focus:fill-green-300
focus:stroke-green-700
"
>
<!-- input box -->
<rect width="150" height="30" rx="4" ry="4">
</rect>
<!-- text within input box -->
<text
x="30"
y="20"
id="proc-one"
class="
invisible
stroke-neutral-950
font-normal
[:target]:visible
"
>
Process One
</text>
<text
x="30"
y="20"
id="proc-two"
class="
invisible
stroke-neutral-950
font-normal
[:target]:visible
"
>
Process Two
</text>
<text
x="30"
y="20"
id="proc-three"
class="
invisible
stroke-neutral-950
font-normal
[:target]:visible
"
>
Process Three
</text>
<rect
x="10"
y="5"
width="15"
height="15"
class="
group-[:has(#proc-one:target)]/root:fill-red-500
group-[:has(#proc-two:target)]/root:fill-blue-500
group-[:has(#proc-three:target)]/root:fill-green-500
"
>
</rect>
</g>
<!--
List of processes
I've tried using `group-[:focus-within]:visible` in the classes to show the list of processes when the user clicks the process selector, but then there's no easy way to hide the list when the user clicks on a process, and still have the `click` event fire.
That is:
`group-[#process_selector_box:focus-within]:visible` does not work (`#process_selector_box` doesn't trigger the CSS selectors).
`group-[:first-child:focus-within]:visible` *does* trigger the CSS selectors, but then the `click` event doesn't fire.
-->
<!-- Process 1 -->
<a
tabindex="1"
href="#proc-one"
class="
invisible
fill-blue-200
hover:fill-green-200
focus:fill-green-300
group-[:focus-within]:visible
"
transform="translate(0 40)"
>
<rect width="100" height="30" />
<text x="10" y="15" class="stroke-slate-900 font-normal">proc one</text>
</a>
<a
tabindex="1"
href="#proc-two"
class="
invisible
fill-blue-200
hover:fill-green-200
focus:fill-green-300
group-[:focus-within]:visible
"
transform="translate(0 80)"
>
<rect width="100" height="30" />
<text x="10" y="15" class="stroke-slate-900 font-normal">proc two</text>
</a>
<a
tabindex="1"
href="#proc-three"
class="
invisible
fill-blue-200
hover:fill-green-200
focus:fill-green-300
group-[:focus-within]:visible
"
transform="translate(0 120)"
>
<rect width="100" height="30" />
<text x="10" y="15" class="stroke-slate-900 font-normal">proc three</text>
</a>
<a
tabindex="1"
href="#clear"
class="
invisible
fill-blue-200
hover:fill-green-200
focus:fill-green-300
group-[:focus-within]:visible
"
transform="translate(0 160)"
>
<rect width="100" height="30" />
<text x="10" y="15" class="stroke-slate-900 font-normal">clear selection</text>
</a>
</g>
<!-- Rectangle that responds to steps being clicked -->
<g
tabindex="4"
class="
fill-slate-400
peer-focus/proc_1_step_1:fill-red-400
peer-focus/proc_1_step_2:fill-red-500
peer-focus/proc_2_step_1:fill-blue-400
peer-focus/proc_2_step_2:fill-blue-500
peer-focus/proc_3_step_1:fill-green-400
peer-focus/proc_3_step_2:fill-green-500
peer-focus/proc_3_step_3:fill-green-600
"
>
<rect x="250" y="60" width="60" height="50" />
</g>
</svg>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment