About Identify browser Multi-page site Interactive fiction
Components Gallery Menu bar Accordion Modal window Toggle
Design Moving background Parallax background Lava / Liquid effect Displacement
Text Typing text

Hi! My name is Kirill Live, and I develop web applications for creativity and work.

For security reasons, many users disable JavaScript in their browsers. I often create small documentation websites where easy navigation and often offline functionality are crucial, and without scripting and back-end this is no easy task. So, I decided to gather all my experience in creating interactive websites without JS in one place.

I've observed that many developers rely too heavily on JavaScript when creating interfaces and often implement functionality that is already available in HTML and CSS without additional scripting. CSS has evolved significantly over the past 20 years, and is no longer just a tool for designers. It now has a wealth of functionality that can do a lot.

This site's repository on GitHub


You can support the project on Patreon or Boosty!



Multi-page site

Using the :target pseudo-class, you can style elements of an HTML page using the URL hash (the part of the URL that follows the pound sign #).

With its underlay, you can hide and show significant portions of a page's content. This allows you to transform a single-page website into a fully-fledged site with multiple pages that can be linked to via URLs, without the need for a backend or JavaScript.

This method isn't suitable for large portals with a lot of content or user-generated content. It's primarily suitable for smaller informational sites like documentation, portfolios or landing pages.

Download multi-page site

HTML

<html> <head> <meta charset="UTF-8"> <title>CSS only</title> <style> /* switching pages using url has (By default, the #home page is displayed) */ :not(:has(.page:target)) #home{display:block;} /* page style */ .page{ display:none; height:400px; } .page:target{display:block;} </style> </head> <body> <!-- page switch button --> <a href='#home' >Home</a> <a href='#info'>Info</a> <a href='#about'>About</a> <!-- content pages --> <div class="page" id="home" style="background-color:#faf">Home page contents</div> <div class="page" id="info" style="background-color:#faa">info page contents</div> <div class="page" id="about" style="background-color:#afa">about page contents</div> </body> </html>

Accordion ( details / summary )

item 1

item 1 html contents

item 2

item 2 html contents

CSS Animation

.accordion details { overflow:hidden; } .accordion details::details-content { height:0px; transition: height 1s, content-visibility 1s allow-discrete; } .accordion details[open]::details-content { height:128px; }

CSS Marker

.accordion details summary { display: flex; justify-content: space-between; } .accordion summary::after {content:'open';} .accordion details[open] summary::after {content:'close';}

HTML

<div class="accordion"> <details open> <summary>item1</summary> <p>item 1 contents</p> </details> <details> <summary>item 2</summary> <p>item 2 contents</p> </details> </div>

Moving background

CSS Animation

/* Animation and direction of background change */ @keyframes moving_bg {100%{background-position:-128px 128px;}} .moving_bg{ /* Image and other background options */ background: #000 url("data:image/svg+xml,%3csvg width='128' height='128' viewBox='0 0 128 128' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='64' height='64' fill='%23fff'/%3e%3crect x='64' y='64' width='64' height='64' fill='%23fff'/%3e%3c/svg%3e") repeat 0 0; /* Animation title, time and style */ animation:moving_bg 6s infinite linear; }

Displacement Map

Works only in Chromium.

Download displacement map SVG

HTML SVG

<!-- SVG Filter --> <svg width="0" height="0"> <filter id="liquid-glass"> <!-- Image displacement or normal map --> <feImage result="noise" preserveAspectRatio="none" href="data:image/svg+xml,%3csvg id='myMap' width='100%' height='100%' preserveAspectRatio='none' xmlns='http://www.w3.org/2000/svg'%3e %3cdefs%3e %3cstyle type='text/css'%3e.redGradient{fill:url(%23redGradient);}.blueGradient{fill: url(%23blueGradient);mix-blend-mode:screen;}.redSymbol{display: block;}.rectgray{width:calc(100% - 32px);height:calc(100% - 32px);x:16px;y:16px;}%3c/style%3e%3cfilter id='shadow2'%3e%3cfeGaussianBlur in='SourceGraphic' stdDeviation='8' /%3e %3c/filter%3e %3clinearGradient id='redGradient' x1='0' x2='1' y1='0' y2='0' color-interpolation='sRGB' gradientUnits='objectBoundingBox'%3e%3cstop offset='30%' stop-color='%23F77' /%3e%3cstop offset='70%' stop-color='%23F44' stop-opacity='0' /%3e%3c/linearGradient%3e%3clinearGradient id='blueGradient' x1='0' x2='0' y1='0' y2='1' color-interpolation='sRGB' gradientUnits='objectBoundingBox'%3e%3cstop offset='20%' stop-color='%2300F' /%3e%3cstop offset='80%' stop-color='%2308F' stop-opacity='0' /%3e%3c/linearGradient%3e%3crect class='redGradient' id='redGradientRect' width='100%' height='100%' x='0' y='0'/%3e%3c/defs%3e%3crect fill='%238B8B8B' width='100%' height='100%' x='0' y='0'/%3e%3cuse class='redSymbol' href='%23redGradientRect'/%3e%3crect class='blueGradient' fill='url(%23blueGradient)' width='100%' height='100%' x='0' y='0' /%3e%3crect class='rectgray' fill='%23BAB' filter='url(%23shadow2)'/%3e%3c/svg%3e"></feImage> <!-- filter parameters --> <feDisplacementMap in="SourceGraphic" in2="MAP_RESULT" scale="80" xChannelSelector="R" yChannelSelector="B" /> </filter> </svg> <!-- An element of reconciliation --> <div style="backdrop-filter:url(#liquid-glass);"></div>


Displacement Noise

Works only in Chromium.

HTML SVG

<!-- SVG Filter --> <svg width="0" height="0"> <filter id="liquid-noise"> <!-- Fractal noise map --> <feTurbulence type="fractalNoise" baseFrequency="0.02" numOctaves="3" result="noise" /> <!-- filter parameters --> <feDisplacementMap in="SourceGraphic" in2="MAP_RESULT" scale="80" xChannelSelector="R" yChannelSelector="G" /> </filter> </svg> <!-- An element of reconciliation --> <div style="backdrop-filter:url(#liquid-noise);"></div>

Full guide: https://kube.io/blog/liquid-glass-css-svg/

Parallax background

The perspective and translateZ functions may work differently in different browsers and produce different effects when combined with other CSS properties.

CSS for overflow

/* styles for galleries */ .parallax_bg { height: 256px; width: 512px; margin-bottom:512px; overflow: hidden auto; /* the Z depth parameter, which creates perspective within the element */ perspective: 2px; } .parallax_bg > .front { /* Determines the depth of the element, which affects scroll speed */ transform: translateZ(0); background: rgba(0,0,0,.2); font-size:20px; top: 25%; width: 100%; height: 50%; position: absolute; } .parallax_bg > .back { font-size:42px; background:#AAF; /* Determines the depth of the element, which affects scroll speed */ transform: translateZ(-2px); width: 301%; height: 401%; top: -50%; left: -50%; position: absolute; }

CSS for body

body{ margin:0px; } .parallax { height: 100vh; overflow:hidden auto; /* the Z depth parameter, which creates perspective within the element */ perspective: 1px; } .back { position: absolute; background:#AAF; /* Determines the depth of the element, which affects scroll speed */ transform: translateZ(-2px); width: 301%; height: 301vh; top: -100vh; left: -100vw; padding-top:256px; font-size: 50px; } .front { position: absolute; /* Determines the depth of the element, which affects scroll speed */ transform: translateZ(0); top: 0px; left 0px; width: 100%; height: 200vh; padding-top:128px; }

HTML

<div class="parallax_bg"> <div class="back">back</div> <div class="front">front</div> </div>

Typing text

Although many modern typewriters have one of several similar designs, as with the automobile, the telephone, and telegraph, several people contributed insights and inventions that eventually resulted in ever more commercially successful instruments.
Historians have estimated that some form of the typewriter was invented 52 times as thinkers and tinkerers tried to come up with a workable design.

HTML

<!-- It is advisable to specify the number of characters in --n: --> <span class="type_writer" style="--n:66">Text to display</span>

CSS Animation

.type_writer { font-family: monospace; font-size: 18px; color: transparent; /* color and style of displaying characters */ background:no-repeat left top / calc(var(--n)*1ch) 100% linear-gradient(#000); background-clip: text; /* speed and type of animation */ animation: show_symbols calc(var(--n)*.1s) steps(var(--n)) forwards; } @keyframes show_symbols{from{background-size:0 100%}}

Full guide: https://dev.to/afif/a-multi-line-css-only-typewriter-effect-3op3

Lava / Liquid effect

SVG

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1280" height="640" viewBox="0 0 1280 640"> <defs> <!-- Blur filter and color correction by matrix --> <filter id="filter"> <feGaussianBlur in="SourceGraphic" stdDeviation="16" result="blur" /> <feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 28 -10" result="filter" /> <feComposite in="SourceGraphic" in2="filter" operator="atop" /> </filter> </defs> <!-- calcMode and keySplines: Animation function "ease" --> <g filter="url(#filter)" fill="#fff"> <circle cx="160" cy="160" r="50"> <animate attributeName="r" begin="0s" dur="18s" values="40;60;40" repeatCount="indefinite" calcMode="spline" keySplines="0.7 0.3 0.3 0.7;0.7 0.3 0.3 0.7" /> <animate attributeName="cx" begin="0s" dur="7s" values="100;120;100" repeatCount="indefinite" calcMode="spline" keySplines="0.7 0.3 0.3 0.7;0.7 0.3 0.3 0.7" /> <animate attributeName="cy" begin="0s" dur="11s" values="110;170;110" repeatCount="indefinite" calcMode="spline" keySplines="0.7 0.3 0.3 0.7;0.7 0.3 0.3 0.7" /> </circle> <circle cx="160" cy="160" r="50"> <animate attributeName="r" begin="0s" dur="8s" values="40;60;40" repeatCount="indefinite" calcMode="spline" keySplines="0.7 0.3 0.3 0.7;0.7 0.3 0.3 0.7" /> <animate attributeName="cx" begin="0s" dur="17s" values="200;120;200" repeatCount="indefinite" calcMode="spline" keySplines="0.7 0.3 0.3 0.7;0.7 0.3 0.3 0.7" /> </circle> <circle cx="160" cy="160" r="50"> <animate attributeName="r" begin="0s" dur="8s" values="65;35;65" repeatCount="indefinite" calcMode="spline" keySplines="0.7 0.3 0.3 0.7;0.7 0.3 0.3 0.7" /> <animate attributeName="cx" begin="0s" dur="9s" values="200;120;200" repeatCount="indefinite" calcMode="spline" keySplines="0.7 0.3 0.3 0.7;0.7 0.3 0.3 0.7" /> <animate attributeName="cy" begin="0s" dur="10s" values="200;120;200" repeatCount="indefinite" calcMode="spline" keySplines="0.7 0.3 0.3 0.7;0.7 0.3 0.3 0.7" /> </circle> </g> </svg>

Interactive Fiction / Visual Novel

CSS capabilities are sufficient to create a simple interactive story with the ability to choose a storyline. For more complex genres, such as dating sims, the capabilities of CSS HTML are not sufficient.

Download interactive fiction

HTML

<html> <head> <style> /* scene switching */ :not(:has(.scen:target)) #scen_1{display:block;} #visual_novel .scen:target{display:block;} /* element for show history */ #visual_novel{ overflow:hidden; height:480px;width:800px; } #visual_novel *{ font-family: monospace; font-size: 18px; box-sizing: border-box; } /* scene element */ #visual_novel .scen{ height:100%;width:100%; background: no-repeat center / cover url(''); display:none; position:relative; } /* switching dialogues in a scene */ #visual_novel input {display: none; } #visual_novel input:checked + label { display:block; pointer-events: auto; } /* show a dialog*/ #visual_novel label { pointer-events: none; width:100%; height:100%; display: none; color:AFF; } /* link to the next scene */ #visual_novel a:not(.choice) { text-decoration:none; height:100%; width:100%; display:block; cursor: default; } /* showing element text */ #visual_novel .text_block{ height:128px; width:calc(100% - 32px); background:#fff; padding:8px; bottom:16px; left:16px; position:absolute; } /* choice buttons */ #visual_novel .choice{ background:#fff; min-width:64px; position:absolute; display:flex; align-items: center; justify-content: center; text-decoration:none; padding:8px; } /* text animation */ #visual_novel .type_writer { color: transparent; background:no-repeat left top / calc(var(--n)*1ch) 100% linear-gradient(#000); background-clip: text; animation: show_symbols calc(var(--n)*.1s) steps(var(--n)) forwards; } @keyframes show_symbols{from{background-size:0 100%}} /* sprites in the scene */ #visual_novel .sprite{ height:100%; width:50%; display:block; background:no-repeat center bottom / contain url("data:image/svg+xml,%3c?xml version='1.0' encoding='UTF-8' standalone='no'?%3e%3csvg xmlns='http://www.w3.org/2000/svg' xml:space='preserve' height='132.24px' width='124.19px' version='1.1' x='0px' y='0px' xmlns:ns='&%2338;ns_ai;' enable-background='new 0 0 124.189 132.243' viewBox='0 0 124.189 132.243'%3e%3cg ns:extraneous='self'%3e%3cpath stroke-width='1.25' d='m62.096 8.5859c-5.208 0-9.424 4.2191-9.424 9.4261 0.001 5.203 4.217 9.424 9.424 9.424 5.202 0 9.422-4.221 9.422-9.424 0-5.208-4.22-9.4261-9.422-9.4261zm-10.41 21.268c-6.672 0-12.131 5.407-12.131 12.07v29.23c0 2.275 1.791 4.123 4.07 4.123 2.28 0 4.127-1.846 4.127-4.123v-26.355h2.102s0.048 68.811 0.048 73.331c0 3.05 2.478 5.53 5.532 5.53 3.052 0 5.525-2.48 5.525-5.53v-42.581h2.27v42.581c0 3.05 2.473 5.53 5.531 5.53 3.054 0 5.549-2.48 5.549-5.53v-73.331h2.127v26.355c0 2.275 1.85 4.123 4.126 4.123 2.28 0 4.073-1.846 4.073-4.123v-29.23c0-6.663-5.463-12.07-12.129-12.07h-20.82z'/%3e%3c/g%3e%3c/svg%3e"); position:absolute; } </style> </head> <body> <div id="visual_novel"> <div id="scen_1" class="scen" style="background-image:linear-gradient(#9198e5 , #e66465)"> <input type="radio" id="text_1" name="scen_1" checked> <label for="text_2"> <div class="sprite" style="top:-10%; left:4%;"></div> <div class="text_block"> <span class="type_writer" style="--n:8">text 1</span> </div> </label> <input type="radio" id="text_2" name="scen_1"> <label for="text_3"> <div class="sprite" style="top:-10%; left:8%;"></div> <div class="text_block"> <span class="type_writer" style="--n:8">text 2</span> </div> </label> <input type="radio" id="text_3" name="scen_1"> <label> <a href='#scen_2'> <div class="sprite" style="top:-10%; left:12%;"></div> <div class="text_block"> <span class="type_writer" style="--n:8">text 3</span> </div> </a> </label> </div> <div id="scen_2" class="scen" style="background-image:linear-gradient(#9198e5 , #e66465)"> <input type="radio" id="text_4" name="scen_2" checked> <label for="text_5"> <div class="sprite" style="top:-10%; left:16%;"></div> <div class="text_block"> <span class="type_writer" style="--n:8">text 4</span> </div> </label> <input type="radio" id="text_5" name="scen_2"> <label for="text_6"> <div class="sprite" style="top:-10%; left:20%;"></div> <div class="text_block"> <span class="type_writer" style="--n:8">text 5</span> </div> </label> <input type="radio" id="text_6" name="scen_2"> <label> <a href='#scen_yes' class="choice" style="top:100px;left:100px;">yes</a> <a href='#scen_no' class="choice" style="top:100px;right:100px;">no</a> <div> <div class="sprite" style="top:-10%; left:24%;"></div> <div class="text_block"> <span class="type_writer" style="--n:8">choice</span> </div> </div> </label> </div> <div id="scen_yes" class="scen" style="background-image:linear-gradient(#9198e5 , #e66465)"> <div> <div class="sprite" style="top:-10%; left:28%;"></div> <div class="text_block"> <span class="type_writer" style="--n:16">choice yes</span> </div> </div> </div> <div id="scen_no" class="scen" style="background-image:linear-gradient(#9198e5 , #e66465)">= <div> <div class="sprite" style="top:-10%; left:28%;"></div> <div class="text_block"> <span class="type_writer" style="--n:16">choice no</span> </div> </div> </div> </div> </body> </html>

Identify browser

WebKit and Gecko are fairly common engines, but they differ in how they display content and the features available. You can determine which engine is being used using @media queries.

CSS

@media screen and (-webkit-min-device-pixel-ratio:0) { body { background-color:#aaf;} body:after{content:"WebKit";} } @media screen and (min--moz-device-pixel-ratio:0) { body { background-color:#afa;} body:after{content:"FireFox";} }

Additionally, you can check in CSS whether a property, rule, or selector is supported by the browser. If the condition is met, the CSS code written within the curly braces will be executed.

CSS

@supports (display: grid) and (grid-template-columns: subgrid) { body:after{ content:"\"Display: Grid\" is supported."; } }

Full guide @supports: https://developer.mozilla.org/

Toggle

CSS

.toggle input{display:none;} .toggle label{ position:relative; display:block; background-color:#faa; width:46px; height:24px; } .toggle label:before{ position:absolute; display:block; content:""; height:20px; width:20px; left:2px; top:2px; background-color:#f00; transition:.5s; } .toggle input:checked ~ label{background-color:#afa;} .toggle input:checked ~ label:before{left:calc(100% - 22px);background-color:#0f0;}

HTML

<div class="toggle"> <input type='checkbox' id="switchbox"> <label class='togle' for="switchbox"></label> </div>