02언더라인 내비
메뉴 글자 아래로 파란 밑줄 하나가 활성 탭을 따라 옆칸으로 부드럽게 미끄러져 이동합니다. 알약도 배경 채움도 없이 밑줄 하나로만 위치를 알려주는 절제된 방식입니다.
쓰임랜딩 상단바, 대시보드 탭 전환
translateX 이동slideUnderlineradius pill

<main class="stage">
<nav class="navtrust">
<span class="navtrust__highlight" aria-hidden="true"></span>
<span class="navtrust__tab">홈</span>
<span class="navtrust__tab">기능</span>
<span class="navtrust__tab">요금제</span>
<span class="navtrust__underline" aria-hidden="true"></span>
</nav>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: 3s; // 세 칸을 도는 한 바퀴 — 2s 로프($dur-loop)보다 정거장이 많아 별도 토큰(합치지 않는다, blog-design 규칙 2)
$easing: $ease-out;
$color: $subject-blue;
$trust-graytint: #f1f5f9;
$trust-ink: $subject-ink;
$trust-blue: $subject-blue;
@include stage($trust-graytint);
// 탭 3개 — 폭을 고정(flex:0 0 auto)해 인디케이터 translateX 이동량과 어긋나지 않는다 (BD19). 320px 안에 들어가도록 전체 폭을 284px 로 미리 잡는다
$tab-w: 92px;
$tab-h: 48px;
$underline-w: 28px;
$underline-offset: 32px; // ($tab-w - $underline-w) / 2, 나눗셈 없이 상수로
.navtrust {
position: relative;
box-sizing: border-box;
display: flex;
align-items: center;
width: $tab-w * 3 + $sp-2;
height: $tab-h;
padding: 0 $sp-1;
border-radius: $r-pill;
background: $stage-paper;
box-shadow: 0 10px 22px rgba($trust-ink, .08);
}
.navtrust__highlight {
position: absolute;
left: $sp-1;
top: 2px;
width: $tab-w - 4px;
height: $tab-h - 4px;
border-radius: $r-control;
background: rgba($trust-blue, .14);
animation: slideTab $duration $easing infinite;
}
.navtrust__tab {
position: relative;
z-index: 1;
flex: 0 0 $tab-w;
text-align: center;
color: $trust-ink;
font-weight: 600; font-size: clamp(11px, 3vw, 13px); line-height: 1;
font-family: inherit;
}
.navtrust__underline {
position: absolute;
bottom: -$sp-2;
left: 0;
width: $underline-w;
height: 3px;
border-radius: $r-xs;
background: $trust-blue;
transform: translateX($underline-offset);
animation: slideUnderline $duration $easing infinite;
}
// 데모: 하이라이트 블록(넓은 면적)과 밑줄(얇은 신호선)이 같은 박자로 세 칸을 순서대로 돈다
@keyframes slideTab {
0%, 22% { transform: translateX(0); }
33%, 55% { transform: translateX($tab-w); }
66%, 88% { transform: translateX($tab-w * 2); }
100% { transform: translateX(0); }
}
@keyframes slideUnderline {
0%, 22% { transform: translateX($underline-offset); }
33%, 55% { transform: translateX($tab-w + $underline-offset); }
66%, 88% { transform: translateX($tab-w * 2 + $underline-offset); }
100% { transform: translateX($underline-offset); }
}
@media (prefers-reduced-motion: reduce) {
.navtrust__highlight, .navtrust__underline { animation: none !important; }
.navtrust__highlight { transform: translateX(0); }
.navtrust__underline { transform: translateX($underline-offset); }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #f1f5f9;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.navtrust {
position: relative;
box-sizing: border-box;
display: flex;
align-items: center;
width: 284px;
height: 48px;
padding: 0 4px;
border-radius: 999px;
background: #ffffff;
box-shadow: 0 10px 22px rgba(23, 20, 26, 0.08);
}
.navtrust__highlight {
position: absolute;
left: 4px;
top: 2px;
width: 88px;
height: 44px;
border-radius: 12px;
background: rgba(47, 109, 246, 0.14);
animation: slideTab 3s ease-out infinite;
}
.navtrust__tab {
position: relative;
z-index: 1;
flex: 0 0 92px;
text-align: center;
color: #17141a;
font-weight: 600;
font-size: clamp(11px, 3vw, 13px);
line-height: 1;
font-family: inherit;
}
.navtrust__underline {
position: absolute;
bottom: -8px;
left: 0;
width: 28px;
height: 3px;
border-radius: 4px;
background: #2f6df6;
transform: translateX(32px);
animation: slideUnderline 3s ease-out infinite;
}
@keyframes slideTab {
0%, 22% {
transform: translateX(0);
}
33%, 55% {
transform: translateX(92px);
}
66%, 88% {
transform: translateX(184px);
}
100% {
transform: translateX(0);
}
}
@keyframes slideUnderline {
0%, 22% {
transform: translateX(32px);
}
33%, 55% {
transform: translateX(124px);
}
66%, 88% {
transform: translateX(216px);
}
100% {
transform: translateX(32px);
}
}
@media (prefers-reduced-motion: reduce) {
.navtrust__highlight, .navtrust__underline {
animation: none !important;
}
.navtrust__highlight {
transform: translateX(0);
}
.navtrust__underline {
transform: translateX(32px);
}
}import type { CSSProperties } from "react";
import styles from "./NavTrust.module.scss";
export type NavTrustProps = {
tabs?: string[];
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function NavTrust({
tabs = ["홈", "기능", "요금제"],
duration = 3000,
easing = "ease-out",
color = "#2f6df6",
}: NavTrustProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<nav className={styles.navtrust} style={vars}>
<span className={styles.highlight} aria-hidden="true" />
{tabs.map((t) => (
<span key={t} className={styles.tab}>{t}</span>
))}
<span className={styles.underline} aria-hidden="true" />
</nav>
);
}02 언더라인 내비 — 측정 누적면적 8.271% · 움직인 프레임 9/23GODRICH 원작 · zip 에 SCSS·React 포함 · saas 랜딩페이지 신뢰감 디자인 키트, 클린 섹션 9개
이 데모가 속한 글
saas 랜딩페이지 신뢰감 디자인 키트, 클린 섹션 9개saas 랜딩페이지 디자인은 흰색과 오프화이트 두 톤 배경 위에 파란색 브랜드 컬러 하나만 얹고, 그림자는 회색 계열로 얕게, 모서리는 절제된 반경으로 통일해 화려하지 않으면서도 진지해 보이게 만드는 스타일입니다. 이 글의 데모 전부 보기


