02スライスインジケーターナビ
アクティブなタブを示すライム色のウェッジ型インジケーターが隣へ移動するとき横に伸び、到着すると弾力よく縮みます。
使いどころLP上部バー・カテゴリタブ切替
scaleY 스쿼시translateX 이동radius pill

<main class="stage">
<nav class="navcitrus" aria-label="メインメニュー">
<span class="navcitrus__brand">スクイーズ</span>
<div class="navcitrus__track">
<span class="navcitrus__wedge" aria-hidden="true"></span>
<span class="navcitrus__tab navcitrus__tab--on">ホーム</span>
<span class="navcitrus__tab">機能</span>
<span class="navcitrus__tab">料金</span>
<span class="navcitrus__tab">お問い合わせ</span>
</div>
<button class="navcitrus__cta" type="button">はじめる</button>
</nav>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 웨지가 네 칸을 한 바퀴 도는 주기
$easing: $ease-spring; // 도착할 때 탄력 있게 줄어드는 곡선
$color: $stage-ink; // 글자 기본(토큰) — 아래 잉크로 덮는다
$citrus-cream: #fffaf0;
$citrus-orange: #d8c1b1;
$citrus-orange-deep: #bd967a;
$citrus-lime: #c4ce9c;
$citrus-lime-deep: #98af5c;
$citrus-grapefruit: #ebd6da;
$citrus-ink: #204a17;
// 무대 — 라임 물색
@include stage(mix($citrus-lime, $stage-paper, 26%));
.navcitrus {
box-sizing: border-box;
width: clamp(280px, 92vw, 440px);
display: flex;
align-items: center;
gap: $sp-2;
padding: $sp-2;
border-radius: $r-pill;
background: $stage-paper;
box-shadow: 0 8px 20px rgba($citrus-lime-deep, .4);
}
.navcitrus__brand {
flex: 0 0 auto;
padding-left: $sp-3;
color: $citrus-ink;
font-weight: 700; font-size: clamp(11px, 2.6vw, 13px); line-height: 1;
font-family: inherit;
}
.navcitrus__track {
position: relative;
flex: 1 1 auto;
min-width: 0;
display: flex;
border-radius: $r-pill;
background: mix($citrus-cream, $stage-paper, 60%);
}
.navcitrus__wedge {
position: absolute;
top: $sp-1;
bottom: $sp-1;
left: $sp-1;
width: calc(25% - $sp-1);
box-sizing: border-box;
border-radius: $r-pill;
background: $citrus-lime-deep;
box-shadow: 0 4px 10px rgba($citrus-lime-deep, .8);
// 네 칸을 순서대로 옮겨 다니며 도착할 때마다 조각(웨지)처럼 세로로 살짝 눌린다
animation: wedgeWalk $duration $easing infinite;
}
.navcitrus__tab {
position: relative;
z-index: 1;
flex: 1 1 auto;
min-width: 0;
padding: $sp-2 0;
text-align: center;
color: rgba($citrus-ink, .78);
font-weight: 600; font-size: clamp(10px, 2.4vw, 12px); line-height: 1;
font-family: inherit;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.navcitrus__tab--on {
color: $citrus-ink;
font-weight: 700;
}
.navcitrus__cta {
flex: 0 0 auto;
border: 0;
border-radius: $r-pill;
background: $citrus-orange-deep;
color: $subject-ink;
padding: $sp-2 $sp-4;
font-weight: 700; font-size: clamp(10px, 2.4vw, 12px); line-height: 1;
font-family: inherit;
box-shadow: 0 5px 12px rgba($citrus-orange-deep, .6);
}
.navcitrus__cta:focus-visible {
outline: 3px solid $citrus-ink;
outline-offset: 2px;
}
// 데모: 칸 폭은 25% — translateX(100%) 가 정확히 한 칸. 도착 순간 scaleY 로 눌렸다 펴진다
// 데모: % 이동은 이 요소 자기 폭 기준 — 폭이 calc(25% - 4px) 라 한 칸(트랙의 25%)보다 4px 모자라
// 이동량을 calc(100% + 4px) 씩 보정한다(보정 없으면 세 칸을 지나며 12px 밀린다 — 217 nav-candy 와 같은 식으로 실측)
@keyframes wedgeWalk {
0%, 24% { transform: translateX(0) scaleY(1); }
30% { transform: translateX(0) scaleY(.82); }
36%, 49% { transform: translateX(calc(100% + 4px)) scaleY(1); }
55% { transform: translateX(calc(100% + 4px)) scaleY(.82); }
61%, 74% { transform: translateX(calc(200% + 8px)) scaleY(1); }
80% { transform: translateX(calc(200% + 8px)) scaleY(.82); }
86%, 100% { transform: translateX(calc(300% + 12px)) scaleY(1); }
}
@media (prefers-reduced-motion: reduce) {
.navcitrus__wedge { animation: none !important; transform: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: rgb(93.9843137255%, 95.0039215686%, 89.9058823529%);
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.navcitrus {
box-sizing: border-box;
width: clamp(280px, 92vw, 440px);
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
border-radius: 999px;
background: #ffffff;
box-shadow: 0 8px 20px rgba(152, 175, 92, 0.4);
}
.navcitrus__brand {
flex: 0 0 auto;
padding-left: 12px;
color: #204a17;
font-weight: 700;
font-size: clamp(11px, 2.6vw, 13px);
line-height: 1;
font-family: inherit;
}
.navcitrus__track {
position: relative;
flex: 1 1 auto;
min-width: 0;
display: flex;
border-radius: 999px;
background: #fffcf6;
}
.navcitrus__wedge {
position: absolute;
top: 4px;
bottom: 4px;
left: 4px;
width: calc(25% - 4px);
box-sizing: border-box;
border-radius: 999px;
background: #98af5c;
box-shadow: 0 4px 10px rgba(152, 175, 92, 0.8);
animation: wedgeWalk 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.navcitrus__tab {
position: relative;
z-index: 1;
flex: 1 1 auto;
min-width: 0;
padding: 8px 0;
text-align: center;
color: rgba(32, 74, 23, 0.78);
font-weight: 600;
font-size: clamp(10px, 2.4vw, 12px);
line-height: 1;
font-family: inherit;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.navcitrus__tab--on {
color: #204a17;
font-weight: 700;
}
.navcitrus__cta {
flex: 0 0 auto;
border: 0;
border-radius: 999px;
background: #bd967a;
color: #17141a;
padding: 8px 16px;
font-weight: 700;
font-size: clamp(10px, 2.4vw, 12px);
line-height: 1;
font-family: inherit;
box-shadow: 0 5px 12px rgba(189, 150, 122, 0.6);
}
.navcitrus__cta:focus-visible {
outline: 3px solid #204a17;
outline-offset: 2px;
}
@keyframes wedgeWalk {
0%, 24% {
transform: translateX(0) scaleY(1);
}
30% {
transform: translateX(0) scaleY(0.82);
}
36%, 49% {
transform: translateX(calc(100% + 4px)) scaleY(1);
}
55% {
transform: translateX(calc(100% + 4px)) scaleY(0.82);
}
61%, 74% {
transform: translateX(calc(200% + 8px)) scaleY(1);
}
80% {
transform: translateX(calc(200% + 8px)) scaleY(0.82);
}
86%, 100% {
transform: translateX(calc(300% + 12px)) scaleY(1);
}
}
@media (prefers-reduced-motion: reduce) {
.navcitrus__wedge {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./NavCitrus.module.scss";
export type NavCitrusProps = {
brand?: string;
tabs?: string[];
active?: number;
cta?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function NavCitrus({
brand = "스퀴즈",
tabs = ["홈", "기능", "요금제", "문의"],
active = 0,
cta = "시작하기",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#9ad200",
}: NavCitrusProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<nav className={styles.navcitrus} style={vars} aria-label="주요 메뉴">
<span className={styles.brand}>{brand}</span>
<div className={styles.track}>
<span className={styles.wedge} aria-hidden="true" style={{ "--activeIndex": active } as CSSProperties} />
{tabs.map((t, i) => (
<span key={t} className={`${styles.tab} ${i === active ? styles.tabOn : ""}`}>{t}</span>
))}
</div>
<button type="button" className={styles.cta}>{cta}</button>
</nav>
);
}02 スライスインジケーターナビ — 計測 累積面積 6.292% · 動いたフレーム 12/23GODRICH オリジナル · SCSS と React は zip に同梱 · ビタミンカラー デザイン LPキット、シトラスカラーのセクション9選
このデモが属する記事
ビタミンカラー デザイン LPキット、シトラスカラーのセクション9選ビタミンカラー デザインは、オレンジ・ライム・グレープフルーツ・レモンのように彩度の高いフルーツカラーをクリームの地に重ね、カードごとに同系色の影を落として、パステルより一段強く、夏のドリンクブランドのようにみずみずしく見せるスタイルです。 この記事のデモをすべて見る


