51 CSS Buttons49 / 51
CSS Receipt Paper Total Count-Up Button
A thermal receipt whose total counts up from zero when you press it — the number is animated by CSS, using a registered integer property fed into counter-reset and printed through content. Zigzag torn bottom edge, faded thermal ink, monospaced columns.
Published Updated
The code
<div class="cb-49">
<div class="cb-49__stage">
<label class="cb-49__btn">
<input type="checkbox" aria-label="Total the order">
<span class="cb-49__head">Corner Roastery</span>
<span class="cb-49__row"><span>Filter, 12oz</span><span>18.00</span></span>
<span class="cb-49__row"><span>Beans, 250g</span><span>22.60</span></span>
<span class="cb-49__row"><span>Service</span><span>8.00</span></span>
<span class="cb-49__rule" aria-hidden="true"></span>
<span class="cb-49__totalRow">
<span class="cb-49__totalLabel">Total</span>
<span class="cb-49__totalVal">
<span class="cb-49__total" aria-hidden="true"></span>
<span class="cb-49__fallback">$48.60</span>
</span>
</span>
<span class="cb-49__foot">Tap to tally · thank you</span>
</label>
</div>
</div><div class="cb-49">
<div class="cb-49__stage">
<label class="cb-49__btn">
<input type="checkbox" aria-label="Total the order">
<span class="cb-49__head">Corner Roastery</span>
<span class="cb-49__row"><span>Filter, 12oz</span><span>18.00</span></span>
<span class="cb-49__row"><span>Beans, 250g</span><span>22.60</span></span>
<span class="cb-49__row"><span>Service</span><span>8.00</span></span>
<span class="cb-49__rule" aria-hidden="true"></span>
<span class="cb-49__totalRow">
<span class="cb-49__totalLabel">Total</span>
<span class="cb-49__totalVal">
<span class="cb-49__total" aria-hidden="true"></span>
<span class="cb-49__fallback">$48.60</span>
</span>
</span>
<span class="cb-49__foot">Tap to tally · thank you</span>
</label>
</div>
</div>@import url('https://fonts.googleapis.com/css2?family=Courier+Prime:wght@400;700&display=swap');
@property --cb-49-n {
syntax: '<integer>';
inherits: false;
initial-value: 0;
}
.cb-49 {
--bg: #26262a;
--paper: #f7f4ec;
--paper-lo: #e7e2d5;
--ink: #2c2a26;
--muted: #7c776c;
--accent: #1f6f5c;
--mono: "Courier Prime",ui-monospace,"Courier New",Courier,monospace;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: radial-gradient(30rem 20rem at 50% 34%, rgba(255,255,255,.05), transparent 72%), var(--bg);
font-family: var(--mono);
}
@supports (color: oklch(50% 0 0)) {
.cb-49 {
--paper: oklch(97% .012 90);
--paper-lo: oklch(91% .014 88);
--accent: oklch(48% .1 168);
}
}
@media (prefers-color-scheme: light) {
.cb-49:not([data-theme="dark"]) {
--bg: #313136;
}
}
.cb-49[data-theme="light"] {
--bg: #313136;
}
.cb-49,
.cb-49 *,
.cb-49 *::before,
.cb-49 *::after {
box-sizing: border-box;
}
.cb-49__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: clamp(1.5rem,6vw,4rem);
}
.cb-49__btn {
position: relative;
cursor: pointer;
display: grid;
gap: .4rem;
width: min(100%,17rem);
padding: 1.2rem 1.15rem 1.6rem;
color: var(--ink);
font-size: .8125rem;
line-height: 1.5;
font-variant-numeric: tabular-nums;
background-color: var(--paper);
background-image: radial-gradient(.05rem .05rem at 30% 40%, rgba(90,84,70,.14) 99%, transparent), linear-gradient(180deg, var(--paper), var(--paper-lo));
background-size: 5px 5px, 100% 100%;
-webkit-mask: conic-gradient(from 135deg at 50% 0, #000 90deg, transparent 0) 0 100%/.76rem .4rem repeat-x, linear-gradient(#000,#000) 0 0/100% calc(100% - .38rem) no-repeat;
mask: conic-gradient(from 135deg at 50% 0, #000 90deg, transparent 0) 0 100%/.76rem .4rem repeat-x, linear-gradient(#000,#000) 0 0/100% calc(100% - .38rem) no-repeat;
filter: drop-shadow(0 12px 18px rgba(0,0,0,.5));
transition: translate .3s cubic-bezier(.16,1,.3,1), filter .3s ease;
-webkit-tap-highlight-color: transparent;
}
.cb-49__btn input {
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
clip-path: inset(50%);
}
.cb-49__head {
text-align: center;
font-weight: 700;
letter-spacing: .18em;
text-transform: uppercase;
font-size: .75rem;
}
.cb-49__row {
display: flex;
justify-content: space-between;
gap: 1rem;
color: var(--muted);
}
.cb-49__rule {
display: block;
height: 0;
border-top: 1px dashed color-mix(in oklab,var(--ink) 35%,transparent);
margin: .35rem 0;
}
.cb-49__totalRow {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 1rem;
}
.cb-49__totalLabel {
font-weight: 700;
letter-spacing: .12em;
text-transform: uppercase;
font-size: .75rem;
}
.cb-49__totalVal {
position: relative;
font-size: 1.5rem;
font-weight: 700;
letter-spacing: -.02em;
color: var(--accent);
}
.cb-49__total {
display: none;
}
.cb-49__fallback {
display: inline;
}
@supports (counter-reset: x var(--cb-49-n)) {
.cb-49__total {
display: inline;
counter-reset: cb-49-a calc(var(--cb-49-n) / 100) cb-49-b calc(var(--cb-49-n) - (var(--cb-49-n) / 100) * 100);
}
.cb-49__total::after {
content: "$" counter(cb-49-a) "." counter(cb-49-b, decimal-leading-zero);
}
.cb-49__fallback {
display: none;
}
}
.cb-49__foot {
text-align: center;
font-size: .6875rem;
color: var(--muted);
letter-spacing: .06em;
}
.cb-49__btn:has(input:checked) .cb-49__total {
animation: cb-49-count 1.5s cubic-bezier(.2,.9,.2,1) forwards;
}
@keyframes cb-49-count {
from {
--cb-49-n: 0;
}
to {
--cb-49-n: 4860;
}
}
.cb-49__btn:has(input:checked) .cb-49__rule {
border-top-style: solid;
}
.cb-49__btn:is(:hover,:has(input:focus-visible)) {
translate: 0 -2px;
filter: drop-shadow(0 18px 24px rgba(0,0,0,.55));
}
.cb-49__btn:has(input:focus-visible) {
outline: 2px solid var(--accent);
outline-offset: 4px;
}
.cb-49__btn:active {
translate: 0 1px;
}
@media (prefers-reduced-motion: reduce) {
.cb-49__btn {
transition: none;
}
.cb-49__btn:has(input:checked) .cb-49__total {
animation: none;
--cb-49-n: 4860;
}
}
@media (forced-colors: active) {
.cb-49__btn {
background: Canvas;
color: CanvasText;
border: 1px solid CanvasText;
mask: none;
-webkit-mask: none;
filter: none;
}
.cb-49__totalVal {
color: CanvasText;
}
}@import url('https://fonts.googleapis.com/css2?family=Courier+Prime:wght@400;700&display=swap');
@property --cb-49-n {
syntax: '<integer>';
inherits: false;
initial-value: 0;
}
.cb-49 {
--bg: #26262a;
--paper: #f7f4ec;
--paper-lo: #e7e2d5;
--ink: #2c2a26;
--muted: #7c776c;
--accent: #1f6f5c;
--mono: "Courier Prime",ui-monospace,"Courier New",Courier,monospace;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: radial-gradient(30rem 20rem at 50% 34%, rgba(255,255,255,.05), transparent 72%), var(--bg);
font-family: var(--mono);
}
@supports (color: oklch(50% 0 0)) {
.cb-49 {
--paper: oklch(97% .012 90);
--paper-lo: oklch(91% .014 88);
--accent: oklch(48% .1 168);
}
}
@media (prefers-color-scheme: light) {
.cb-49:not([data-theme="dark"]) {
--bg: #313136;
}
}
.cb-49[data-theme="light"] {
--bg: #313136;
}
.cb-49,
.cb-49 *,
.cb-49 *::before,
.cb-49 *::after {
box-sizing: border-box;
}
.cb-49__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: clamp(1.5rem,6vw,4rem);
}
.cb-49__btn {
position: relative;
cursor: pointer;
display: grid;
gap: .4rem;
width: min(100%,17rem);
padding: 1.2rem 1.15rem 1.6rem;
color: var(--ink);
font-size: .8125rem;
line-height: 1.5;
font-variant-numeric: tabular-nums;
background-color: var(--paper);
background-image: radial-gradient(.05rem .05rem at 30% 40%, rgba(90,84,70,.14) 99%, transparent), linear-gradient(180deg, var(--paper), var(--paper-lo));
background-size: 5px 5px, 100% 100%;
-webkit-mask: conic-gradient(from 135deg at 50% 0, #000 90deg, transparent 0) 0 100%/.76rem .4rem repeat-x, linear-gradient(#000,#000) 0 0/100% calc(100% - .38rem) no-repeat;
mask: conic-gradient(from 135deg at 50% 0, #000 90deg, transparent 0) 0 100%/.76rem .4rem repeat-x, linear-gradient(#000,#000) 0 0/100% calc(100% - .38rem) no-repeat;
filter: drop-shadow(0 12px 18px rgba(0,0,0,.5));
transition: translate .3s cubic-bezier(.16,1,.3,1), filter .3s ease;
-webkit-tap-highlight-color: transparent;
}
.cb-49__btn input {
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
clip-path: inset(50%);
}
.cb-49__head {
text-align: center;
font-weight: 700;
letter-spacing: .18em;
text-transform: uppercase;
font-size: .75rem;
}
.cb-49__row {
display: flex;
justify-content: space-between;
gap: 1rem;
color: var(--muted);
}
.cb-49__rule {
display: block;
height: 0;
border-top: 1px dashed color-mix(in oklab,var(--ink) 35%,transparent);
margin: .35rem 0;
}
.cb-49__totalRow {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 1rem;
}
.cb-49__totalLabel {
font-weight: 700;
letter-spacing: .12em;
text-transform: uppercase;
font-size: .75rem;
}
.cb-49__totalVal {
position: relative;
font-size: 1.5rem;
font-weight: 700;
letter-spacing: -.02em;
color: var(--accent);
}
.cb-49__total {
display: none;
}
.cb-49__fallback {
display: inline;
}
@supports (counter-reset: x var(--cb-49-n)) {
.cb-49__total {
display: inline;
counter-reset: cb-49-a calc(var(--cb-49-n) / 100) cb-49-b calc(var(--cb-49-n) - (var(--cb-49-n) / 100) * 100);
}
.cb-49__total::after {
content: "$" counter(cb-49-a) "." counter(cb-49-b, decimal-leading-zero);
}
.cb-49__fallback {
display: none;
}
}
.cb-49__foot {
text-align: center;
font-size: .6875rem;
color: var(--muted);
letter-spacing: .06em;
}
.cb-49__btn:has(input:checked) .cb-49__total {
animation: cb-49-count 1.5s cubic-bezier(.2,.9,.2,1) forwards;
}
@keyframes cb-49-count {
from {
--cb-49-n: 0;
}
to {
--cb-49-n: 4860;
}
}
.cb-49__btn:has(input:checked) .cb-49__rule {
border-top-style: solid;
}
.cb-49__btn:is(:hover,:has(input:focus-visible)) {
translate: 0 -2px;
filter: drop-shadow(0 18px 24px rgba(0,0,0,.55));
}
.cb-49__btn:has(input:focus-visible) {
outline: 2px solid var(--accent);
outline-offset: 4px;
}
.cb-49__btn:active {
translate: 0 1px;
}
@media (prefers-reduced-motion: reduce) {
.cb-49__btn {
transition: none;
}
.cb-49__btn:has(input:checked) .cb-49__total {
animation: none;
--cb-49-n: 4860;
}
}
@media (forced-colors: active) {
.cb-49__btn {
background: Canvas;
color: CanvasText;
border: 1px solid CanvasText;
mask: none;
-webkit-mask: none;
filter: none;
}
.cb-49__totalVal {
color: CanvasText;
}
}Here's a working CSS Button from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Receipt Paper Total Count-Up Button
Source: https://codefronts.com/components/css-buttons/css-receipt-paper-total-count-up-button/
A thermal receipt whose total counts up from zero when you press it — the number is animated by CSS, using a registered integer property fed into counter-reset and printed through content. Zigzag torn bottom edge, faded thermal ink, monospaced columns.
## HTML
```html
<div class="cb-49">
<div class="cb-49__stage">
<label class="cb-49__btn">
<input type="checkbox" aria-label="Total the order">
<span class="cb-49__head">Corner Roastery</span>
<span class="cb-49__row"><span>Filter, 12oz</span><span>18.00</span></span>
<span class="cb-49__row"><span>Beans, 250g</span><span>22.60</span></span>
<span class="cb-49__row"><span>Service</span><span>8.00</span></span>
<span class="cb-49__rule" aria-hidden="true"></span>
<span class="cb-49__totalRow">
<span class="cb-49__totalLabel">Total</span>
<span class="cb-49__totalVal">
<span class="cb-49__total" aria-hidden="true"></span>
<span class="cb-49__fallback">$48.60</span>
</span>
</span>
<span class="cb-49__foot">Tap to tally · thank you</span>
</label>
</div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Courier+Prime:wght@400;700&display=swap');
@property --cb-49-n {
syntax: '<integer>';
inherits: false;
initial-value: 0;
}
.cb-49 {
--bg: #26262a;
--paper: #f7f4ec;
--paper-lo: #e7e2d5;
--ink: #2c2a26;
--muted: #7c776c;
--accent: #1f6f5c;
--mono: "Courier Prime",ui-monospace,"Courier New",Courier,monospace;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: radial-gradient(30rem 20rem at 50% 34%, rgba(255,255,255,.05), transparent 72%), var(--bg);
font-family: var(--mono);
}
@supports (color: oklch(50% 0 0)) {
.cb-49 {
--paper: oklch(97% .012 90);
--paper-lo: oklch(91% .014 88);
--accent: oklch(48% .1 168);
}
}
@media (prefers-color-scheme: light) {
.cb-49:not([data-theme="dark"]) {
--bg: #313136;
}
}
.cb-49[data-theme="light"] {
--bg: #313136;
}
.cb-49,
.cb-49 *,
.cb-49 *::before,
.cb-49 *::after {
box-sizing: border-box;
}
.cb-49__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: clamp(1.5rem,6vw,4rem);
}
.cb-49__btn {
position: relative;
cursor: pointer;
display: grid;
gap: .4rem;
width: min(100%,17rem);
padding: 1.2rem 1.15rem 1.6rem;
color: var(--ink);
font-size: .8125rem;
line-height: 1.5;
font-variant-numeric: tabular-nums;
background-color: var(--paper);
background-image: radial-gradient(.05rem .05rem at 30% 40%, rgba(90,84,70,.14) 99%, transparent), linear-gradient(180deg, var(--paper), var(--paper-lo));
background-size: 5px 5px, 100% 100%;
-webkit-mask: conic-gradient(from 135deg at 50% 0, #000 90deg, transparent 0) 0 100%/.76rem .4rem repeat-x, linear-gradient(#000,#000) 0 0/100% calc(100% - .38rem) no-repeat;
mask: conic-gradient(from 135deg at 50% 0, #000 90deg, transparent 0) 0 100%/.76rem .4rem repeat-x, linear-gradient(#000,#000) 0 0/100% calc(100% - .38rem) no-repeat;
filter: drop-shadow(0 12px 18px rgba(0,0,0,.5));
transition: translate .3s cubic-bezier(.16,1,.3,1), filter .3s ease;
-webkit-tap-highlight-color: transparent;
}
.cb-49__btn input {
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
clip-path: inset(50%);
}
.cb-49__head {
text-align: center;
font-weight: 700;
letter-spacing: .18em;
text-transform: uppercase;
font-size: .75rem;
}
.cb-49__row {
display: flex;
justify-content: space-between;
gap: 1rem;
color: var(--muted);
}
.cb-49__rule {
display: block;
height: 0;
border-top: 1px dashed color-mix(in oklab,var(--ink) 35%,transparent);
margin: .35rem 0;
}
.cb-49__totalRow {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 1rem;
}
.cb-49__totalLabel {
font-weight: 700;
letter-spacing: .12em;
text-transform: uppercase;
font-size: .75rem;
}
.cb-49__totalVal {
position: relative;
font-size: 1.5rem;
font-weight: 700;
letter-spacing: -.02em;
color: var(--accent);
}
.cb-49__total {
display: none;
}
.cb-49__fallback {
display: inline;
}
@supports (counter-reset: x var(--cb-49-n)) {
.cb-49__total {
display: inline;
counter-reset: cb-49-a calc(var(--cb-49-n) / 100) cb-49-b calc(var(--cb-49-n) - (var(--cb-49-n) / 100) * 100);
}
.cb-49__total::after {
content: "$" counter(cb-49-a) "." counter(cb-49-b, decimal-leading-zero);
}
.cb-49__fallback {
display: none;
}
}
.cb-49__foot {
text-align: center;
font-size: .6875rem;
color: var(--muted);
letter-spacing: .06em;
}
.cb-49__btn:has(input:checked) .cb-49__total {
animation: cb-49-count 1.5s cubic-bezier(.2,.9,.2,1) forwards;
}
@keyframes cb-49-count {
from {
--cb-49-n: 0;
}
to {
--cb-49-n: 4860;
}
}
.cb-49__btn:has(input:checked) .cb-49__rule {
border-top-style: solid;
}
.cb-49__btn:is(:hover,:has(input:focus-visible)) {
translate: 0 -2px;
filter: drop-shadow(0 18px 24px rgba(0,0,0,.55));
}
.cb-49__btn:has(input:focus-visible) {
outline: 2px solid var(--accent);
outline-offset: 4px;
}
.cb-49__btn:active {
translate: 0 1px;
}
@media (prefers-reduced-motion: reduce) {
.cb-49__btn {
transition: none;
}
.cb-49__btn:has(input:checked) .cb-49__total {
animation: none;
--cb-49-n: 4860;
}
}
@media (forced-colors: active) {
.cb-49__btn {
background: Canvas;
color: CanvasText;
border: 1px solid CanvasText;
mask: none;
-webkit-mask: none;
filter: none;
}
.cb-49__totalVal {
color: CanvasText;
}
}
```Here's a working CSS Button from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Receipt Paper Total Count-Up Button
Source: https://codefronts.com/components/css-buttons/css-receipt-paper-total-count-up-button/
A thermal receipt whose total counts up from zero when you press it — the number is animated by CSS, using a registered integer property fed into counter-reset and printed through content. Zigzag torn bottom edge, faded thermal ink, monospaced columns.
## HTML
```html
<div class="cb-49">
<div class="cb-49__stage">
<label class="cb-49__btn">
<input type="checkbox" aria-label="Total the order">
<span class="cb-49__head">Corner Roastery</span>
<span class="cb-49__row"><span>Filter, 12oz</span><span>18.00</span></span>
<span class="cb-49__row"><span>Beans, 250g</span><span>22.60</span></span>
<span class="cb-49__row"><span>Service</span><span>8.00</span></span>
<span class="cb-49__rule" aria-hidden="true"></span>
<span class="cb-49__totalRow">
<span class="cb-49__totalLabel">Total</span>
<span class="cb-49__totalVal">
<span class="cb-49__total" aria-hidden="true"></span>
<span class="cb-49__fallback">$48.60</span>
</span>
</span>
<span class="cb-49__foot">Tap to tally · thank you</span>
</label>
</div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Courier+Prime:wght@400;700&display=swap');
@property --cb-49-n {
syntax: '<integer>';
inherits: false;
initial-value: 0;
}
.cb-49 {
--bg: #26262a;
--paper: #f7f4ec;
--paper-lo: #e7e2d5;
--ink: #2c2a26;
--muted: #7c776c;
--accent: #1f6f5c;
--mono: "Courier Prime",ui-monospace,"Courier New",Courier,monospace;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: radial-gradient(30rem 20rem at 50% 34%, rgba(255,255,255,.05), transparent 72%), var(--bg);
font-family: var(--mono);
}
@supports (color: oklch(50% 0 0)) {
.cb-49 {
--paper: oklch(97% .012 90);
--paper-lo: oklch(91% .014 88);
--accent: oklch(48% .1 168);
}
}
@media (prefers-color-scheme: light) {
.cb-49:not([data-theme="dark"]) {
--bg: #313136;
}
}
.cb-49[data-theme="light"] {
--bg: #313136;
}
.cb-49,
.cb-49 *,
.cb-49 *::before,
.cb-49 *::after {
box-sizing: border-box;
}
.cb-49__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: clamp(1.5rem,6vw,4rem);
}
.cb-49__btn {
position: relative;
cursor: pointer;
display: grid;
gap: .4rem;
width: min(100%,17rem);
padding: 1.2rem 1.15rem 1.6rem;
color: var(--ink);
font-size: .8125rem;
line-height: 1.5;
font-variant-numeric: tabular-nums;
background-color: var(--paper);
background-image: radial-gradient(.05rem .05rem at 30% 40%, rgba(90,84,70,.14) 99%, transparent), linear-gradient(180deg, var(--paper), var(--paper-lo));
background-size: 5px 5px, 100% 100%;
-webkit-mask: conic-gradient(from 135deg at 50% 0, #000 90deg, transparent 0) 0 100%/.76rem .4rem repeat-x, linear-gradient(#000,#000) 0 0/100% calc(100% - .38rem) no-repeat;
mask: conic-gradient(from 135deg at 50% 0, #000 90deg, transparent 0) 0 100%/.76rem .4rem repeat-x, linear-gradient(#000,#000) 0 0/100% calc(100% - .38rem) no-repeat;
filter: drop-shadow(0 12px 18px rgba(0,0,0,.5));
transition: translate .3s cubic-bezier(.16,1,.3,1), filter .3s ease;
-webkit-tap-highlight-color: transparent;
}
.cb-49__btn input {
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
clip-path: inset(50%);
}
.cb-49__head {
text-align: center;
font-weight: 700;
letter-spacing: .18em;
text-transform: uppercase;
font-size: .75rem;
}
.cb-49__row {
display: flex;
justify-content: space-between;
gap: 1rem;
color: var(--muted);
}
.cb-49__rule {
display: block;
height: 0;
border-top: 1px dashed color-mix(in oklab,var(--ink) 35%,transparent);
margin: .35rem 0;
}
.cb-49__totalRow {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 1rem;
}
.cb-49__totalLabel {
font-weight: 700;
letter-spacing: .12em;
text-transform: uppercase;
font-size: .75rem;
}
.cb-49__totalVal {
position: relative;
font-size: 1.5rem;
font-weight: 700;
letter-spacing: -.02em;
color: var(--accent);
}
.cb-49__total {
display: none;
}
.cb-49__fallback {
display: inline;
}
@supports (counter-reset: x var(--cb-49-n)) {
.cb-49__total {
display: inline;
counter-reset: cb-49-a calc(var(--cb-49-n) / 100) cb-49-b calc(var(--cb-49-n) - (var(--cb-49-n) / 100) * 100);
}
.cb-49__total::after {
content: "$" counter(cb-49-a) "." counter(cb-49-b, decimal-leading-zero);
}
.cb-49__fallback {
display: none;
}
}
.cb-49__foot {
text-align: center;
font-size: .6875rem;
color: var(--muted);
letter-spacing: .06em;
}
.cb-49__btn:has(input:checked) .cb-49__total {
animation: cb-49-count 1.5s cubic-bezier(.2,.9,.2,1) forwards;
}
@keyframes cb-49-count {
from {
--cb-49-n: 0;
}
to {
--cb-49-n: 4860;
}
}
.cb-49__btn:has(input:checked) .cb-49__rule {
border-top-style: solid;
}
.cb-49__btn:is(:hover,:has(input:focus-visible)) {
translate: 0 -2px;
filter: drop-shadow(0 18px 24px rgba(0,0,0,.55));
}
.cb-49__btn:has(input:focus-visible) {
outline: 2px solid var(--accent);
outline-offset: 4px;
}
.cb-49__btn:active {
translate: 0 1px;
}
@media (prefers-reduced-motion: reduce) {
.cb-49__btn {
transition: none;
}
.cb-49__btn:has(input:checked) .cb-49__total {
animation: none;
--cb-49-n: 4860;
}
}
@media (forced-colors: active) {
.cb-49__btn {
background: Canvas;
color: CanvasText;
border: 1px solid CanvasText;
mask: none;
-webkit-mask: none;
filter: none;
}
.cb-49__totalVal {
color: CanvasText;
}
}
```How this works
@property with an integer syntax makes a number animatable; counter-reset accepts that number, and a counter can be printed in generated content. Chain them and you get a JavaScript-free count-up:
@property --cb-49-n{ syntax:'<integer>'; inherits:false; initial-value:0; }
.cb-49__total{ counter-reset:cb-49-total var(--cb-49-n); }
.cb-49__total::after{ content:'$' counter(cb-49-total); }
.cb-49__btn:has(input:checked) .cb-49__total{
animation:cb-49-count 1.4s cubic-bezier(.2,.9,.2,1) forwards;
}
@keyframes cb-49-count{ to{ --cb-49-n:4860; } }Because the counter is an integer, cents are handled by counting hundredths and letting a sibling render the decimal point — the value is announced separately as real text in a live region for assistive tech.
The zigzag tear is a two-layer mask: a .4rem strip of downward triangles at the bottom, unioned with a base that covers the rest — conic-gradient(from 135deg at 50% 0, #000 90deg, transparent 0) is one tooth per tile; the thermal look is a slightly warm paper with two faded bands and a fine dot grain, and every column uses tabular-nums so the receipt aligns like a real one.
Make it yours
- Change the target with the keyframe's end value; keep it in hundredths if you display cents.
- Adjust the easing for a different feel: a slow-out curve reads as a machine tallying, linear reads as a stopwatch.
- Change the tear pitch with the mask's tile width.
- Add line items by copying a row — the grid is two columns with
tabular-nums.
Gotchas — read before shipping
- Animated counters are Chrome/Safari-only today. Always leave the final value as real text in the DOM as the fallback (this demo does), or the total reads as $0 in Firefox.
- A number animated in
contentis not reliably announced by screen readers — the live region carries the real value. @propertyinteger animation rounds per frame; for currency, count hundredths, never floats.counter-resetre-declares on each animation frame — keep the counter name demo-scoped or it will collide.- Do not put important information only in
::aftercontent; some assistive tech ignores generated content entirely.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Floor set by :has(), oklch(), color-mix(), @property as this demo is written. The core technique itself goes back further — Chrome 85+.
@property: Chrome 85 / Safari 16.4 / Firefox 128. Animated counter printing via content works in Chrome and Safari; Firefox shows the static fallback total. :has() needs Chrome 105 / Safari 15.4 / Firefox 121.