In this blog, we will show you how to create a neomorphic Tic Tac Toe game using only HTML and CSS. Neomorphic design, also known as soft UI, is a trending design style that gives a 3D effect to the elements on the screen, making them appear more interactive and engaging. By the end of this tutorial, you’ll have a fully functional Tic Tac Toe game with a sleek neomorphic design that will take your game experience to the next level. So, let’s get started!
Before we start here are some more games you might like to create using css only –
- Neomorphic Tic Tac Toe Game using HTML and CSS Only
- Whac a mole game using HTML and CSS Only
- Plankman Game using HTML and CSS Only
- Rock Paper Scissor Game using HTML and CSS Only
- Carnival Target Practice Game using HTML and CSS Only
I would recommend you don’t just copy and paste the code, just look at the code and type by understanding it.
Demo
See the Pen Pure CSS/SVG Tic Tac Toe 😎 by Jhey (@jh3y) on CodePen.
HTML (PUG) Code
mixin confetti() - let c = 0 while c < 10 .confetti(style=`--rotation: ${(Math.random() * 180) - 90}; --travel: ${Math.random() * -100};`) 🎉 - c++ mixin cross() svg(style!=attributes.style class!=attributes.class viewBox="0 0 100 100") path.cross(d="M 80 20 L 20 80" fill="none" stroke-width="10" stroke-linecap="round" stroke-dasharray="100" stroke-dashoffset="100") path.cross(d="M 20 20 L 80 80" fill="none" stroke-width="10" stroke-linecap="round" stroke-dasharray="100" stroke-dashoffset="100") mixin naught() svg(class!=attributes.class style!=attributes.style viewBox="0 0 100 100") circle(cx="50" cy="50" r="30" fill="none" stroke-width="10" stroke-linecap="round" stroke-dasharray="200" stroke-dashoffset="200") form.game - for (let i = 0; i < 9; i++) input(type="checkbox" id=`x-${i}`) - const x = i % 3 - const y = Math.floor(i / 3) +cross()(class="x board__x" style=`--x: ${x}; --y: ${y};`) path.cross(d="M 80 20 L 20 80" fill="none" stroke-width="10" stroke-linecap="round" stroke-dasharray="100" stroke-dashoffset="100") path.cross(d="M 20 20 L 80 80" fill="none" stroke-width="10" stroke-linecap="round" stroke-dasharray="100" stroke-dashoffset="100") input(type="checkbox" id=`o-${i}`) +naught()(class="o board__o" style=`--x: ${x}; --y: ${y};`) .board - const DELAYS = [0.75, 0.5, 1, 0.25] - for (let l = 0; l < 4; l++) - const rotate = l % 2 - const shift = 2 % (l + 1) ? 1 : -1 - const delay = DELAYS[l] svg.board__line(viewBox="0 0 10 300" style=`--rotate: ${rotate}; --shift: ${shift}; --delay: ${delay};`) path(d="M 5 5 L 5 295" stroke-width="10" stroke-linecap="round" stroke-dasharray="300" stroke-dashoffset="300") - for (let i = 0; i < 9; i++) .board__cell label(for=`x-${i}`) +cross()(class="x ghost") label(for=`o-${i}`) +naught()(class="o ghost") .game__result.game__result--x.result +confetti() .result__content .result__title Winner! +cross()(class="x result__winner") .game__result.game__result--o.result +confetti() .result__content .result__title Winner! +naught()(class="o result__winner") .game__result.game__result--draw.result .result__content .result__title Draw... svg.zzz.result__winner(viewBox="0 0 24 24") path(d="M23,12H17V10L20.39,6H17V4H23V6L19.62,10H23V12M15,16H9V14L12.39,10H9V8H15V10L11.62,14H15V16M7,20H1V18L4.39,14H1V12H7V14L3.62,18H7V20Z") button(type="reset" title="Reset Board") svg.reset(viewBox="0 0 24 24") path(d="M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z")
CSS (Stylus) Code
Create a file style.css and paste the code below.
@font-face { font-family: Cyber; src: url("https://assets.codepen.io/605876/Blender-Pro-Bold.otf"); font-display: swap; } * box-sizing border-box :root --size 300px --piece-size calc(var(--size) / 3) --line hsl(0, 0%, 90%) --bg hsl(210, 50%, 5%) --naught hsl(150, 80%, 70%) --naught-alpha hsla(150, 80%, 70%, 0.5) --cross hsl(280, 80%, 70%) --cross-alpha hsla(280, 80%, 70%, 0.5) --draw-speed 0.15 --color hsl(0, 10%, 15%) @media(min-width 768px) --size 50vmin @media(max-height 500px) --size 300px body min-height 100vh display grid place-items center margin 0 overflow hidden background var(--bg) color var(--theme-text-color) font-family 'Cyber', sans-serif text-transform uppercase svg filter drop-shadow(0 -0.25vmin 0.25vmin hsl(0, 10%, 0%)) drop-shadow(0 0 0.5vmin var(--alpha)) drop-shadow(0 0 1vmin var(--alpha)) drop-shadow(0 0 5vmin var(--stroke)) brightness(1.2) stroke var(--stroke) form .board display grid place-items center position relative .game__result display none position absolute width calc(var(--size) * 1.5) height calc(var(--size) * 1.5) transform translate(-50%, -50%) top 50% left 50% label .o .x position absolute display inline-block height var(--piece-size) width var(--piece-size) label cursor pointer &:hover .ghost opacity 0.5 .ghost opacity 0 transition opacity calc(var(--draw-speed) * 1s) .o --alpha var(--naught-alpha) --stroke var(--naught) transform rotateX(180deg) .x --alpha var(--cross-alpha) --stroke var(--cross) path:nth-of-type(2) --delay var(--draw-speed) :checked + .x display block :checked + .o display block .board height var(--size) width var(--size) grid-template-columns repeat(3, 1fr) grid-template-rows repeat(3, 1fr) &__x &__o display none left calc(var(--x) * (100% / 3)) top calc(var(--y) * (100% / 3)) z-index 2 position absolute &__line --stroke var(--line) --alpha hsla(0, 0%, 90%, 0.5) width calc(var(--size) * 0.05) height var(--size) position absolute top 50% left 50% transform translate(-50%, -50%) rotate(calc(var(--rotate) * -90deg)) translate(calc(var(--shift) * ((var(--size) / 3) * 0.5)), 0) &__cell height var(--piece-size) width var(--piece-size) input button position absolute button top 125% background transparent border 0 padding 0 height 5vmin width 5vmin min-height 48px min-width 48px outline transparent cursor pointer display none transition transform calc(var(--draw-speed) * 1s) animation fadeIn calc(var(--draw-speed) * 4s) calc(var(--draw-speed) * 2s) both &:hover transform translate(0, -4%) &:active transform translate(0, 2%) scale(0.8) .reset height 100% fill var(--line) input position fixed left 100% .result animation flyIn calc(var(--draw-speed) * 3s) ease-in both backdrop-filter blur(25px) z-index 10 &__content height 40% width 40% top 50% left 50% transform translate(-50%, -50%) position absolute border-radius 15% background hsla(210, 30%, 20%, 0.8) color hsl(0, 0%, 100%) align-items center display flex justify-content center flex-direction column font-weight bold font-size 2rem box-shadow 0 3vmin 2.5vmin -2.5vmin hsl(0, 0%, 0%) &__winner position static height calc(var(--size) / 3) .zzz --stroke hsl(210, 80%, 50%) fill var(--stroke) @keyframes fadeIn from opacity 0 @keyframes flyIn from opacity 0 transform translate(-50%, 250%) scale(0) // This part only cares about showing/hiding the right labels for each move :checked ~ .board .board__cell label:nth-of-type(odd) :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(odd) :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(odd) :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(odd) :checked ~ :checked ~ .board .board__cell label:nth-of-type(even) :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(even) :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(even) :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(even) // opacity 1 display block :checked ~ .board .board__cell label:nth-of-type(even) :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(even) :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(even) :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(even) :checked ~ :checked ~ .board .board__cell label:nth-of-type(odd) :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(odd) :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(odd) :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(odd) // opacity 0.25 display none // Winning combos #x-0:checked ~ #x-1:checked ~ #x-2:checked ~ .game__result--x #x-3:checked ~ #x-4:checked ~ #x-5:checked ~ .game__result--x #x-6:checked ~ #x-7:checked ~ #x-8:checked ~ .game__result--x #x-0:checked ~ #x-3:checked ~ #x-6:checked ~ .game__result--x #x-1:checked ~ #x-4:checked ~ #x-7:checked ~ .game__result--x #x-2:checked ~ #x-5:checked ~ #x-8:checked ~ .game__result--x #x-0:checked ~ #x-4:checked ~ #x-8:checked ~ .game__result--x #x-2:checked ~ #x-4:checked ~ #x-6:checked ~ .game__result--x #o-0:checked ~ #o-1:checked ~ #o-2:checked ~ .game__result--o #o-3:checked ~ #o-4:checked ~ #o-5:checked ~ .game__result--o #o-6:checked ~ #o-7:checked ~ #o-8:checked ~ .game__result--o #o-0:checked ~ #o-3:checked ~ #o-6:checked ~ .game__result--o #o-1:checked ~ #o-4:checked ~ #o-7:checked ~ .game__result--o #o-2:checked ~ #o-5:checked ~ #o-8:checked ~ .game__result--o #o-0:checked ~ #o-4:checked ~ #o-8:checked ~ .game__result--o #o-2:checked ~ #o-4:checked ~ #o-6:checked ~ .game__result--o display flex // Edge case if the last move is a winning move. Don't show the draw. & ~ .game__result--draw display none & ~ button display block :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked // if the last move is played and there isn't a winner, show a draw ~ .game__result--draw display block ~ button display block .board__line path .o circle .x path animation draw calc(var(--draw-speed) * 1s) calc(var(--delay, 0) * 1s) ease-in both @keyframes draw to stroke-dashoffset 0 .confetti position absolute top 50% left 50% font-size 2rem animation celebrate 1s forwards, fadeOut calc(var(--draw-speed) * 1s) calc((1 - var(--draw-speed)) * 1s) forwards @keyframes fadeOut to opacity 0 @keyframes celebrate from transform translate(-50%, -50%) rotate(calc(var(--rotation) * 1deg)) scale(0) translate(0, 0) to transform translate(-50%, -50%) rotate(calc(var(--rotation) * 1deg)) scale(1) translate(0, calc(var(--travel) * 1vmin))
Output Till Now
Written by: Piyush Patil
If you found any mistakes or have any doubts please feel free to Contact Us
Hope you find this post helpful💖