04Hand-drawn checklist
Each row's checkmark is a single crayon-style SVG stroke that draws from left to right, and offsetting each row's start time makes the checks complete in sequence — far more tactile than a neat icon font.
Best forFeature lists, checklist steps
stroke-dashoffset 체크어긋난 시작 시각radius 12

<main class="stage">
<ul class="listcrayon">
<li class="listcrayon__row listcrayon__row--1">
<svg class="listcrayon__check" viewBox="0 0 20 16" aria-hidden="true">
<path class="listcrayon__check-path" d="M2 9 C 3 8, 4 10, 7 13 C 10 10, 14 6, 18 2" />
</svg>
<span class="listcrayon__text">Drag to place</span>
</li>
<li class="listcrayon__row listcrayon__row--2">
<svg class="listcrayon__check" viewBox="0 0 20 16" aria-hidden="true">
<path class="listcrayon__check-path" d="M2 9 C 3 8, 4 10, 7 13 C 10 10, 14 6, 18 2" />
</svg>
<span class="listcrayon__text">Pick a color and paint</span>
</li>
<li class="listcrayon__row listcrayon__row--3">
<svg class="listcrayon__check" viewBox="0 0 20 16" aria-hidden="true">
<path class="listcrayon__check-path" d="M2 9 C 3 8, 4 10, 7 13 C 10 10, 14 6, 18 2" />
</svg>
<span class="listcrayon__text">Choose a hand-drawn icon</span>
</li>
<li class="listcrayon__row listcrayon__row--4">
<svg class="listcrayon__check" viewBox="0 0 20 16" aria-hidden="true">
<path class="listcrayon__check-path" d="M2 9 C 3 8, 4 10, 7 13 C 10 10, 14 6, 18 2" />
</svg>
<span class="listcrayon__text">Share right away</span>
</li>
</ul>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 체크가 한 번 그려지는 주기
$easing: $ease-out; // 선이 그려지는 곡선
$color: $stage-ink; // 글자 색 기본값(토큰) — 아래 크레용 잉크로 덮는다
$crayon-paper: #fff8ef;
$crayon-ink: #33261c;
$crayon-red: #dbb6b7;
$crayon-yellow: #d1c5a2;
$crayon-blue: #9cb8ce;
$crayon-green: #6dbe6d;
$crayon-purple: #cbbfe0;
@include stage($crayon-paper);
.listcrayon {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: $sp-2;
width: clamp(220px, 80vw, 300px);
}
.listcrayon__row {
display: flex;
align-items: center;
gap: $sp-3;
border-radius: $r-pill;
background: $stage-paper;
border: 2px solid rgba($crayon-ink, .16);
padding: $sp-2 $sp-4;
}
.listcrayon__check {
flex-shrink: 0;
width: 24px;
height: 20px;
overflow: visible;
}
.listcrayon__check-path {
fill: none;
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 26;
animation: checkDraw $duration $easing infinite;
}
.listcrayon__row--1 .listcrayon__check-path { stroke: $crayon-red; animation-delay: 0ms; }
.listcrayon__row--2 .listcrayon__check-path { stroke: $crayon-blue; animation-delay: 160ms; }
.listcrayon__row--3 .listcrayon__check-path { stroke: $crayon-green; animation-delay: 320ms; }
.listcrayon__row--4 .listcrayon__check-path { stroke: $crayon-purple; animation-delay: 480ms; }
.listcrayon__text {
color: $crayon-ink;
font-weight: 600;
font-size: clamp(11px, 2.8vw, 13px);
}
// 데모: 체크 표시가 왼쪽부터 오른쪽까지 한 획으로 그려졌다 지워지길 반복하며, 행마다 시작이 어긋난다
@keyframes checkDraw {
0% { stroke-dashoffset: 26; opacity: 0; }
6% { opacity: 1; }
45% { stroke-dashoffset: 0; opacity: 1; }
80% { stroke-dashoffset: 0; opacity: 1; }
100% { stroke-dashoffset: -26; opacity: 0; }
}
@media (max-width: 340px), (max-height: 220px) {
.listcrayon { gap: $sp-1; }
.listcrayon__row { padding: $sp-1 $sp-3; gap: $sp-2; }
}
@media (prefers-reduced-motion: reduce) {
.listcrayon__check-path {
animation: none !important;
stroke-dashoffset: 0;
opacity: 1;
}
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #fff8ef;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.listcrayon {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 8px;
width: clamp(220px, 80vw, 300px);
}
.listcrayon__row {
display: flex;
align-items: center;
gap: 12px;
border-radius: 999px;
background: #ffffff;
border: 2px solid rgba(51, 38, 28, 0.16);
padding: 8px 16px;
}
.listcrayon__check {
flex-shrink: 0;
width: 24px;
height: 20px;
overflow: visible;
}
.listcrayon__check-path {
fill: none;
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 26;
animation: checkDraw 2s ease-out infinite;
}
.listcrayon__row--1 .listcrayon__check-path {
stroke: #dbb6b7;
animation-delay: 0ms;
}
.listcrayon__row--2 .listcrayon__check-path {
stroke: #9cb8ce;
animation-delay: 160ms;
}
.listcrayon__row--3 .listcrayon__check-path {
stroke: #6dbe6d;
animation-delay: 320ms;
}
.listcrayon__row--4 .listcrayon__check-path {
stroke: #cbbfe0;
animation-delay: 480ms;
}
.listcrayon__text {
color: #33261c;
font-weight: 600;
font-size: clamp(11px, 2.8vw, 13px);
}
@keyframes checkDraw {
0% {
stroke-dashoffset: 26;
opacity: 0;
}
6% {
opacity: 1;
}
45% {
stroke-dashoffset: 0;
opacity: 1;
}
80% {
stroke-dashoffset: 0;
opacity: 1;
}
100% {
stroke-dashoffset: -26;
opacity: 0;
}
}
@media (max-width: 340px), (max-height: 220px) {
.listcrayon {
gap: 4px;
}
.listcrayon__row {
padding: 4px 12px;
gap: 8px;
}
}
@media (prefers-reduced-motion: reduce) {
.listcrayon__check-path {
animation: none !important;
stroke-dashoffset: 0;
opacity: 1;
}
}import type { CSSProperties } from "react";
import styles from "./FeatureListCrayon.module.scss";
export type FeatureListCrayonProps = {
items?: string[];
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const DEFAULT_ITEMS = ["드래그로 배치", "색상 골라 칠하기", "손그림 아이콘 고르기", "바로 공유하기"];
const COLORS = ["#ff5a5f", "#4fa3e3", "#6dbe6d", "#a685e2"];
export default function FeatureListCrayon({
items = DEFAULT_ITEMS,
duration = 2000,
easing = "ease-out",
color = "#ff5a5f",
}: FeatureListCrayonProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<ul className={styles.list} style={vars}>
{items.map((text, i) => (
<li key={text} className={styles.row}>
<svg className={styles.check} viewBox="0 0 20 16" aria-hidden="true">
<path
className={styles.checkPath}
style={{ stroke: COLORS[i % COLORS.length], animationDelay: `${i * 160}ms` }}
d="M2 9 C 3 8, 4 10, 7 13 C 10 10, 14 6, 18 2"
/>
</svg>
<span className={styles.text}>{text}</span>
</li>
))}
</ul>
);
}04 Hand-drawn checklist — Measured area 0.372% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · Crayon Web Design: 9 Hand-Drawn Landing Sections
The post this demo lives in
Crayon Web Design: 9 Hand-Drawn Landing SectionsCrayon web design draws thick, wobbly outlines around cards and buttons and runs an SVG filter on just those outlines, s See all demos in this post


