스큐어모피즘 디자인 랜딩 키트, 질감 부품 9개
스큐어모피즘 디자인은 가죽·금속·나무 같은 실물 질감을 화면에 옮겨 담는 스타일입니다. 사진 한 장 없이 가죽·금속·나무를 CSS 값만으로 만들어, 2024년 이후 제품 사이트에서 한두 군데씩 다시 돌아온 그 질감을 부품 아홉 개로 정리했습니다.
2010년대 초의 앱은 화면 전체를 가죽과 아마포로 감쌌고, 그 무게에 지쳐 플랫 디자인이 왔습니다. 지금 돌아온 질감은 그때와 다릅니다. 밝고 평면적인 화면에 질감을 딱 세 곳만 얹어, 손이 기억하는 재질감을 포인트로 쓰는 방식입니다. 아래 원페이지를 스크롤하면서 어느 자리에 질감이 앉았는지 먼저 찾아보세요.
저 페이지를 이루는 부품 아홉 개를 하나씩 떼어내 격자에 늘어놓았습니다. 순서는 인기순이 아니라 재료가 무게를 갖는 차례입니다. 가죽을 꿰매고, 금속을 돌리고, 종이를 겹치고, 노브를 만지고, 유리를 박고, 나무를 열고, 스티커를 붙이고, 버튼을 누릅니다. 마지막 아홉 번째는 이 재료들을 한 페이지에 제대로 앉히는 법입니다.
자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip
- 01 가죽 스티치 요금 카드
- 02 금속 다이얼 CTA
- 03 종이 결 기능 카드
- 04 볼륨 노브 위젯
- 05 유리 인레이 배지
- 06 나무 결 미닫이 패널
- 07 스티커 엠보싱 로고
- 08 물리적으로 눌리는 버튼
- 09 질감 세 곳만 쓴 원페이지
01가죽 스티치 요금 카드
가죽 카드가 20px 위에서 내려앉는 순간, 밝은 스티치 눈금이 이중 테두리 안에 한 프레임 컷으로 새겨집니다. 스티치는 이미지가 아니라 네 변을 각각 감싼 repeating-linear-gradient 눈금이라 배경색을 바꿔도 다시 그릴 필요가 없습니다.
.leather__stitch {
position: absolute;
top: 8px; left: 8px; right: 8px; bottom: 8px;
border-radius: $r-control;
opacity: 0;
background:
repeating-linear-gradient(90deg, $stitch 0 8px, transparent 8px 16px) top / 100% 2px no-repeat,
repeating-linear-gradient(90deg, $stitch 0 8px, transparent 8px 16px) bottom / 100% 2px no-repeat,
repeating-linear-gradient(0deg, $stitch 0 8px, transparent 8px 16px) left / 2px 100% no-repeat,
repeating-linear-gradient(0deg, $stitch 0 8px, transparent 8px 16px) right / 2px 100% no-repeat;
animation: stitchCut $duration steps(1, end) infinite;
}
@keyframes stitchCut {
0%, 36% { opacity: 0; }
38%, 86% { opacity: 1; }
88%, 100% { opacity: 0; }
}
02금속 다이얼 CTA
다이얼이 32도씩 좌우로 돌면서 conic-gradient 음영이 표면을 흘러 다니고, 테두리 노치와 지침이 방향을 함께 가리킵니다. 버튼 요소 안에 원반이 통째로 들어 있어 이것 자체가 전환 버튼입니다.
.metaldial__dial {
animation: dialSwing $duration $ease-spring infinite;
}
.metaldial__face {
background:
conic-gradient(from 210deg,
$metal-hi 0deg, $metal-lo 70deg, $metal-mid 120deg,
$metal-hi 180deg, $metal-edge 250deg, $metal-mid 300deg, $metal-hi 360deg);
box-shadow: inset 0 2px 3px rgba(255, 255, 255, .65), inset 0 -3px 5px rgba(0, 0, 0, .4);
}
@keyframes dialSwing {
0%, 6% { transform: rotate(-32deg); }
48%, 56% { transform: rotate(32deg); }
94%, 100% { transform: rotate(-32deg); }
}
03종이 결 기능 카드
결이 보이는 종이 시트 세 장이 포개져 있고, 맨 위 시트가 48px 위로 미끄러져 아래 시트의 결을 드러냅니다. 결은 방향을 섞은 1px repeating-linear-gradient 세 겹이라 노이즈 이미지가 필요 없습니다.
.paperstack__sheet {
background:
repeating-linear-gradient(0deg, $grain-a 0 1px, transparent 1px 3px),
repeating-linear-gradient(90deg, $grain-b 0 1px, transparent 1px 4px),
repeating-linear-gradient(48deg, $grain-c 0 1px, transparent 1px 7px),
$paper-face;
}
@keyframes sheetSlide {
0%, 10% { transform: translateY(0); }
46%, 58% { transform: translateY(-48px); }
88%, 100% { transform: translateY(0); }
}
04볼륨 노브 위젯
노브가 눈금 판의 최솟값에서 최댓값까지 120도씩 양쪽으로 돌고, 밝은 지침 선이 눈금 위를 지나갑니다. 각도가 커져도 노브는 커 보이지 않습니다 — 회전이 원 안에서만 끝나기 때문입니다.
.volknob__knob {
background:
radial-gradient(120% 120% at 34% 26%, rgba(255, 255, 255, .32), transparent 42%),
conic-gradient(from 250deg, $knob-face, $knob-body 90deg, $knob-face 170deg,
$knob-body 260deg, $knob-face 340deg);
box-shadow: 0 6px 10px rgba(23, 20, 26, .35), inset 0 1px 2px rgba(255, 255, 255, .22);
animation: knobSweep $duration $ease-out infinite;
}
@keyframes knobSweep {
0%, 8% { transform: rotate(-120deg); }
46%, 56% { transform: rotate(120deg); }
92%, 100% { transform: rotate(-120deg); }
}
05유리 인레이 배지
브러시드 메탈 판에 파낸 자리에 유리 배지가 박혀 있고, 비스듬한 하이라이트 막대가 왼쪽에서 오른쪽으로 훑고 지나갑니다. 광선이 코어 위를 지나는 순간 코어가 한 번 밝아지는 것까지 포함해 빛이 유리를 통과하는 느낌을 만듭니다.
.glassbadge__sheen {
position: absolute;
top: -20%; left: 0;
width: 44%;
height: 140%;
transform: translateX(-140%) rotate(24deg);
background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .4) 45%,
rgba(255, 255, 255, .55) 50%, rgba(255, 255, 255, .4) 55%, transparent);
animation: sheenSweep $duration $ease-out infinite;
}
@keyframes flareUp {
0%, 36% { opacity: 0; }
44%, 50% { opacity: 1; }
58%, 100% { opacity: 0; }
}
06나무 결 미닫이 패널
나무 결 두 짝이 좌우로 갈라지며 안쪽 밝은 슬롯을 드러내고, 잠시 열린 채로 있다가 다시 닫힙니다. 결 줄무늬는 패널 배경이라 패널이 움직이면 결도 함께 흘러, 사진을 밀어 놓은 것과 같은 효과가 납니다.
.wooddoors__leaf {
background:
radial-gradient(ellipse 14px 22px at 62% 30%, rgba(74, 44, 12, .55) 0 38%,
rgba($wood-c, .5) 42% 52%, transparent 60%),
repeating-linear-gradient(90deg,
$wood-a 0 12px, $wood-b 12px 15px, $wood-c 15px 27px, $wood-d 27px 30px,
$wood-a 30px 38px, $wood-lo 38px 41px);
}
@keyframes doorsOpenL {
0%, 10% { transform: translateX(0); }
42%, 60% { transform: translateX(-92%); }
88%, 100% { transform: translateX(0); }
}
07스티커 엠보싱 로고
엠보싱 로고 스티커가 24px 위에서 기울어진 채 내려와 붙는 자리에 앉습니다. 음각은 text-shadow 네 겹입니다. 아래로 밝은 빛 한 줄, 위로 어두운 그늘 한 줄, 좌우 보정 두 줄 — 이 네 개가 모여야 파인 글자로 읽힙니다.
.emboss__brand {
color: $emboss-ink;
text-shadow:
0 1px 0 $emboss-light,
0 -1px 0 $emboss-dark,
1px 0 0 rgba(255, 255, 255, .55),
-1px 0 0 rgba(96, 74, 48, .35);
}
@keyframes stickerApply {
0% { transform: translateY(-24px) rotate(-8deg) scale(1.12); }
40% { transform: translateY(0) rotate(-3deg) scale(1); }
50% { transform: translateY(-2px) rotate(-3.6deg) scale(1.01); }
60%, 88% { transform: translateY(0) rotate(-3deg) scale(1); }
100% { transform: translateY(-24px) rotate(-8deg) scale(1.12); }
}
08물리적으로 눌리는 버튼
키캡이 8px 내려앉는 동안 밑의 옆벽 요소가 scaleY로 4분의 1로 줄어 누르는 깊이를 만듭니다. 벽을 box-shadow로 그리지 않고 별도 요소로 둔 것은, 눌림 애니메이션을 그림자가 아니라 변환으로 처리하기 위해서입니다.
.physkey__wall {
height: 8px;
transform-origin: 50% 0;
animation: wallPress $duration $ease-spring infinite;
}
@keyframes capPress {
0%, 12% { transform: translateY(0); }
32%, 46% { transform: translateY(8px); }
62% { transform: translateY(-3px); }
76%, 100% { transform: translateY(0); }
}
@keyframes wallPress {
0%, 12% { transform: scaleY(1); }
32%, 46% { transform: scaleY(.25); }
62% { transform: scaleY(1.1); }
76%, 100% { transform: scaleY(1); }
}
09질감 세 곳만 쓴 원페이지
미니 랜딩 페이지 전체가 위아래로 왕복 스크롤되며 구간을 지나갑니다. 질감은 나무 내비·금속 다이얼·가죽 요금 카드 세 곳에만 쓰고 나머지는 밝은 평면입니다. 트랙을 뷰포트 높이의 정확히 2배로 만들어 translateY(-50%)가 한 화면만큼 움직이게 하는 것이 이 프리뷰의 뼈대입니다.
.minipage__track {
display: flex;
flex-direction: column;
height: 200%;
animation: pageScroll $duration $ease-out infinite;
}
@keyframes pageScroll {
0%, 10% { transform: translateY(0); }
44%, 54% { transform: translateY(-50%); }
88%, 100% { transform: translateY(0); }
}
tailwind.config 커스텀
이 스타일에서 값을 한 곳에 모아야 하는 이유는 색이 아니라 눌림의 두께입니다. 물리 키는 쉴 때 옆벽이 8px, 눌렀을 때 2px — 이 두 값이 어긋나면 버튼이 떠 보이거나 박히지 않습니다.
theme: {
extend: {
colors: {
paper: "#fbf6ec", sheet: "#ffffff", ink: "#2a2320", stone: "#5c5048",
brand: "#2f6df6", branddeep: "#1c48b8", mint: "#2f9e74",
leather: "#7a4a2b", leatherdeep: "#5e3517", wood: "#a96f38",
},
borderRadius: { card: "16px", control: "12px", chip: "4px", pill: "999px" },
boxShadow: {
keyrest: "inset 0 2px 0 rgba(255,255,255,.4), inset 0 -2px 0 rgba(0,0,0,.25), 0 8px 0 #1a3fa0",
keydown: "inset 0 2px 0 rgba(255,255,255,.4), inset 0 -2px 0 rgba(0,0,0,.25), 0 2px 0 #1a3fa0",
},
},
},
| 역할 | 토큰 | 언제 |
|---|---|---|
| 키 벽 두께 | shadow-keyrest |
버튼이 쉬고 있을 때 (8px) |
| 눌린 벽 | shadow-keydown |
active: 상태에서만 (2px) |
| 질감면 | tex-leather 등 |
한 페이지에 최대 세 곳 |
바이브코딩 프롬프트
설정을 심었다면 Claude Code나 Cursor에 넘길 지시는 아래 한 덩어리면 충분합니다. 색 목록보다 질감을 어디에 둘 것인가가 먼저 오는 순서가 중요합니다. 생략 없는 원문 전문은 zip 안 prompt.md로 넣었습니다.
질감은 정확히 세 곳에만 쓴다: 내비(나무) · CTA(금속 다이얼) · 요금 추천 플랜(가죽).
질감 없는 구간이 질감 있는 구간의 두 배 이상이어야 한다. 나무는 repeating-linear-gradient
세로 결 줄무늬, 금속 다이얼은 conic-gradient 음영 네 구간, 가죽은 두 방향 repeating 겹침.
스티치 눈금은 네 변을 각각 repeating-linear-gradient 8px 실/8px 간격으로 감싼다.
버튼은 쉴 때 옆벽 8px, 눌릴 때 2px — 벽이 줄어든 만큼 translateY 로 내려앉는다.
이 지시대로 page/index.html을 만들고 1280px 뷰포트에서 전체 페이지를 찍은 것이 아래 그림입니다.

어디서 깨지는가 — 함정
가장 오래 붙들었던 것은 나무 위 글자였습니다. 내비에 흰 브랜드 이름을 바로 얹었는데, 나무 결 밴드가 네 색이라 대비가 밴드마다 달랐습니다. 상대휘도로 직접 계산하니 같은 흰 글자가 어두운 밴드(#96622e)에서는 5.14:1인데 밝은 밴드(#c08a4e)에서는 3.01:1로 떨어졌습니다. 어느 한 가지 색으로는 네 밴드를 전부 넘길 수 없어서, 글자를 지우고 짙은 인레이 칩 위에 올렸고, 나무는 장식으로만 남겼습니다. 질감 위 본문은 대비를 재고 나서 앉히는 것이 원칙입니다.
두 번째는 종이 시트의 이동거리였습니다. 맨 위 시트를 카드 높이의 72퍼센트만큼 올리니 480px 화면에서는 멀쩡한데 320px 화면에서 시트 윗변이 무대를 벗어나 잘렸습니다. 이동거리를 48px로 픽셀 값으로 바꾸고, 좁은 화면에서는 20px로 더 줄여야 두 폭에서 다 시트 전체가 보였습니다. 아홉 개 파일의 완성본은 압축 비밀번호 7dcfwy5m 으로 열리는 zip 안에 그대로 들어 있습니다.
접근성
질감은 대비의 적입니다. WCAG 2.1 최소 대비 기준의 상대휘도 공식으로 이 키트의 글자 조합을 전부 직접 계산해 표로 남겼습니다.
| 글자 조합 | 대비 | 판정 |
|---|---|---|
| 가죽 위 본문 #f6e7cf / 가죽 #7a4a2b | 6.07:1 | 통과 |
| 가죽 위 가격 #ffffff / 가죽 #7a4a2b | 7.39:1 | 통과 |
| 종이 본문 #574a38 / 종이 #fff7e6 | 8.07:1 | 통과 |
| 종이 보조 #7a6c56 / 종이 #fff7e6 | 4.80:1 | 통과 (아슬아슬) |
| 히어로 버튼 #ffffff / 브랜드색 #2f6df6 | 4.53:1 | 통과 (아슬아슬) |
| 가죽 밝은 결 위 본문 #f6e7cf / #96613c | 4.24:1 | 실패 — 밝은 결 위 본문 금지 |
밝은 결 밴드 위에서 본문이 기준 아래로 떨어지는 값이 마지막 줄입니다. 그래서 가죽 카드의 글자는 어두운 결 쪽에만 앉히고, 민트 코어 위 흰 글자(1.86:1)도 아이콘 색을 짙은 청록으로 바꿔 넣었습니다. 움직임은 아홉 개 전부에 MDN prefers-reduced-motion 문서의 미디어 쿼리를 넣어, 동작 줄이기를 켠 방문자에게는 질감이 앉은 정지 상태만 보입니다. 질감 자체는 MDN conic-gradient 문서와 MDN text-shadow 문서의 그라디언트·그림자 값이라 지원하지 않는 브라우저에서도 최악은 단색 면입니다.
어떤 스타일이 지금 뜨는지는 디자인 트렌드 모아보기에서 보시고, 이 사이트가 무엇을 주는 곳인지는 소개에 적어 뒀습니다.
FAQ
질감은 몇 곳까지 넣어도 되나요?
한 페이지에 세 곳까지입니다. 이 키트에서도 나무 내비·금속 다이얼·가죽 요금 카드 세 곳에만 질감을 쓰고 나머지는 밝은 평면으로 뒀습니다. 질감이 네 곳을 넘으면 시선이 쉬는 곳이 사라져 2010년대의 무거운 화면으로 돌아갑니다.
2010년대처럼 화면 전체를 가죽으로 감싸면 안 되나요?
요금 카드 한 장이 목적이라면 가능하지만, 랜딩 전체에는 권하지 않습니다. 배경이 어두워지는 만큼 본문 대비를 계속 재야 하고, 가장 밝은 결 밴드 위에서는 4.5:1을 맞추기 어렵습니다. 질감은 포인트일 때 값이 있고 바탕이 되면 비용이 됩니다.
React 프로젝트에 바로 붙일 수 있나요?
zip 안에 아홉 부품의 React 판(.tsx + .module.scss)이 같이 들어 있습니다. 시간·이징·대표색은 props로 받으므로, 붙인 뒤 값만 바꿔 쓰면 됩니다. 바닐라 판과 React 판은 같은 값으로 함께 검증했습니다.