02アンダーラインナビ
メニュー文字の下で青いアンダーラインがアクティブなタブへなめらかに移動します。pillも塗りもなく、線一本だけで位置を示す抑制の効いた方式です。
使いどころLP上部バー・ダッシュボードのタブ切替
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 オリジナル · SCSS と React は zip に同梱 · saas サイト デザイン 信頼感キット、クリーンなセクション9選
このデモが属する記事
saas サイト デザイン 信頼感キット、クリーンなセクション9選saas サイト デザインの良さは、白とオフホワイトという二つの中立な背景の上に、ブランドカラーを青一色だけ重ね、影は色付きにせずグレー系で浅く、角の丸みも抑えた半径に統一するところにあります。パステルやキャンディグラデーションの明るさとは この記事のデモをすべて見る


