02뜨는 라벨 입력창
빈 칸엔 라벨이 입력칸 안에 놓여 있다가, 포커스나 입력이 생기면 라벨이 위로 떠서 작아집니다.
쓰임로그인 폼, 회원가입 이름·비밀번호 입력
label transform포커스 색 전환채움 상태 유지

<main class="stage">
<div class="fl is-demo" id="wrap">
<div class="fl__control">
<input class="fl__input" id="fl-name" type="text" placeholder=" ">
<label class="fl__label" for="fl-name">이름</label>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-quick; // 라벨이 뜨는 시간
$easing: $ease-spring; // 스프링 이징
$color: $subject-lilac; // 포커스·라벨 강조 색
// ── 데모 무대 ──────────────────────────────────────────
@include stage($stage-yellow);
.fl {
box-sizing: border-box;
width: min(260px, 82vw);
background: #fff;
border-radius: $r-card;
padding: $sp-5;
box-shadow: $shadow-raised;
&__control {
position: relative;
}
&__input {
box-sizing: border-box;
width: 100%;
border: 2px solid rgba(23, 20, 26, .12);
border-radius: $r-control;
padding: $sp-5 $sp-4 $sp-2;
font-weight: 500;
font-size: clamp(14px, 4.2vw, 16px);
font-family: inherit;
color: $stage-ink;
background: #fff;
transition: border-color $duration $easing;
&:focus {
outline: none;
border-color: $color;
}
}
&__label {
position: absolute;
left: $sp-4;
top: 50%;
transform: translateY(-50%) scale(1);
transform-origin: left center; // 규칙8: 라벨 왼쪽 붙어 축소
font-size: 16px;
font-weight: 500;
font-family: inherit;
color: rgba(23, 20, 26, .48);
pointer-events: none;
transition: transform $duration $easing, color $duration $easing;
}
&__input:focus + &__label,
&__input:not(:placeholder-shown) + &__label {
transform: translateY(-14px) scale(.78);
color: $color;
}
}
@media (prefers-reduced-motion: reduce) {
.fl__input,
.fl__label {
transition: none;
}
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffd23f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.fl {
box-sizing: border-box;
width: min(260px, 82vw);
background: #fff;
border-radius: 16px;
padding: 20px;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
}
.fl__control {
position: relative;
}
.fl__input {
box-sizing: border-box;
width: 100%;
border: 2px solid rgba(23, 20, 26, 0.12);
border-radius: 12px;
padding: 20px 16px 8px;
font-weight: 500;
font-size: clamp(14px, 4.2vw, 16px);
font-family: inherit;
color: #17141a;
background: #fff;
transition: border-color 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.fl__input:focus {
outline: none;
border-color: #b9a0ff;
}
.fl__label {
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%) scale(1);
transform-origin: left center;
font-size: 16px;
font-weight: 500;
font-family: inherit;
color: rgba(23, 20, 26, 0.48);
pointer-events: none;
transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1), color 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.fl__input:focus + .fl__label, .fl__input:not(:placeholder-shown) + .fl__label {
transform: translateY(-14px) scale(0.78);
color: #b9a0ff;
}
@media (prefers-reduced-motion: reduce) {
.fl__input,
.fl__label {
transition: none;
}
}(function () {
var wrap = document.getElementById('wrap');
var input = document.getElementById('fl-name');
function stopDemo() {
if (!wrap.classList.contains('is-demo')) return;
wrap.classList.remove('is-demo');
clearInterval(timer);
}
input.addEventListener('pointerdown', stopDemo);
input.addEventListener('keydown', stopDemo);
function fireInput() {
input.dispatchEvent(new Event('input', { bubbles: true }));
}
var word = '홍길동';
var chars = '';
var phase = 'focus';
var pauseUntil = 0;
var timer = setInterval(function () {
var now = Date.now();
if (now < pauseUntil) return;
if (phase === 'focus') {
input.focus({ preventScroll: true });
phase = 'type';
return;
}
if (phase === 'type') {
if (chars.length < word.length) {
chars += word[chars.length];
input.value = chars;
fireInput();
} else {
phase = 'hold';
pauseUntil = now + 260;
}
return;
}
if (phase === 'hold') {
phase = 'clear';
return;
}
if (phase === 'clear') {
chars = '';
input.value = '';
fireInput();
input.blur();
phase = 'blurhold';
pauseUntil = now + 220;
return;
}
if (phase === 'blurhold') {
phase = 'focus';
}
}, 90);
})();import { useState } from "react";
import styles from "./FloatingLabelInput.module.scss";
export type FloatingLabelInputProps = {
label?: string;
};
export default function FloatingLabelInput({ label = "이름" }: FloatingLabelInputProps) {
const [value, setValue] = useState("");
return (
<div className={styles.fl}>
<div className={styles.control}>
<input
className={styles.input}
id="fl-name"
type="text"
placeholder=" "
value={value}
onChange={(e: any) => setValue(e.target.value)}
/>
<label className={styles.label} htmlFor="fl-name">
{label}
</label>
</div>
</div>
);
}02 뜨는 라벨 입력창 — 측정 누적면적 1.207% · 움직인 프레임 9/23GODRICH 원작 · zip 에 SCSS·React 포함 · 회원가입 폼 디자인 입력창 9가지 — 붙이면 바로 되는 코드
이 데모가 속한 글
회원가입 폼 디자인 입력창 9가지 — 붙이면 바로 되는 코드회원가입 폼 디자인에서 사람이 처음으로 손대는 곳은 버튼이 아니라 입력창입니다. 여기 아홉 개는 이메일 칸부터 지우기 버튼까지 회원가입 화면에 실제로 들어가는 입력창이고, 코드를 붙이면 그대로 움직입니다. 이 글의 데모 전부 보기


