TIP
왼쪽 메뉴에서 항목을 탭하면 바로 이동합니다. 검색창에 키워드를 입력하면 메뉴가 필터링됩니다.
애플리케이션 생성
Vue3
Vue3 앱의 진입점. createApp으로 루트 인스턴스를 만들고 마운트한다.
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
Vue2와 차이: Vue2는 new Vue({...}), Vue3는 createApp() 팩토리 함수를 사용. 전역 설정(컴포넌트, 디렉티브)도 app 인스턴스에 등록.
템플릿 문법
Vue3
데이터 바인딩과 디렉티브의 기본 형태.
<!-- 텍스트 보간 -->
<p>{{ message }}</p>
<!-- 속성 바인딩 -->
<img :src="imageUrl" />
<!-- 이벤트 바인딩 -->
<button @click="increment">+1</button>
<!-- 양방향 바인딩 -->
<input v-model="name" />
| 축약 표기 | 전체 표기 |
:src | v-bind:src |
@click | v-on:click |
#default | v-slot:default |
ref / reactive
Vue3면접 빈출
Composition API의 반응형 상태 선언 방식. ref는 원시값+객체 모두 가능, reactive는 객체 전용.
import { ref, reactive } from 'vue'
// ref: .value로 접근 (스크립트 내부에서)
const count = ref(0)
count.value++
// reactive: 객체 자체가 반응형 (proxy)
const state = reactive({ name: 'Eisen', age: 40 })
state.age++
핵심 차이: ref는 내부적으로 .value를 통해 getter/setter를 거치며, 템플릿에서는 자동 언래핑된다. reactive는 구조분해 시 반응성을 잃는다 (이때 toRefs 사용).
computed
Vue3
의존 데이터가 바뀔 때만 재계산되는 캐시된 값.
import { ref, computed } from 'vue'
const firstName = ref('길동')
const lastName = ref('홍')
const fullName = computed(() => `${lastName.value}${firstName.value}`)
주의: computed 내부에서 비동기 작업이나 부수효과(side effect)를 일으키지 말 것. 순수 함수처럼 사용.
watch / watchEffect
Vue3
반응형 데이터 변화를 감지해 부수효과(API 호출, DOM 조작 등)를 실행.
import { ref, watch, watchEffect } from 'vue'
const keyword = ref('')
// 특정 소스를 명시적으로 감시
watch(keyword, (newVal, oldVal) => {
console.log(`${oldVal} → ${newVal}`)
})
// 사용된 반응형 값을 자동 추적
watchEffect(() => {
console.log('현재 검색어:', keyword.value)
})
| 구분 | watch | watchEffect |
| 추적 대상 | 명시적으로 지정 | 콜백 내 사용된 값 자동 추적 |
| 초기 실행 | X (기본값) | O (즉시 실행) |
| 이전 값 접근 | 가능 | 불가 |
v-model
Vue3
폼 입력과 상태의 양방향 바인딩. 내부적으로 :value + @input 조합.
<input v-model="text" />
<!-- 위는 아래와 동일 -->
<input :value="text" @input="text = $event.target.value" />
<!-- 수식어 -->
<input v-model.trim="text" /> // 공백 제거
<input v-model.number="age" /> // 숫자로 변환
v-if / v-for
Vue3면접 빈출
조건부 렌더링과 리스트 렌더링.
<p v-if="score >= 90">A</p>
<p v-else-if="score >= 80">B</p>
<p v-else>F</p>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
금지: v-if와 v-for를 같은 태그에 함께 쓰지 말 것 (Vue3는 v-if가 우선 적용되어 의도와 다르게 동작). 부모에 v-for, 자식에 v-if를 분리하거나 <template> 래핑.
:key 필수: v-for에는 항상 고유한 key를 부여해야 가상 DOM diff가 올바르게 동작한다. index를 key로 쓰면 리스트 순서 변경 시 버그 발생 가능.
props (부모→자식 데이터 전달)
Vue3
// 자식 컴포넌트 (Child.vue)
<script setup>
const props = defineProps({
title: { type: String, required: true },
count: { type: Number, default: 0 }
})
</script>
<!-- 부모 컴포넌트 -->
<Child :title="'안녕'" :count="5" />
주의: props는 자식에서 직접 변경 불가 (단방향 데이터 흐름). 변경이 필요하면 로컬 ref에 복사하거나 emit으로 부모에 알린다.
emit (자식→부모 이벤트)
Vue3
// 자식 컴포넌트
<script setup>
const emit = defineEmits(['update', 'delete'])
function handleClick() {
emit('update', { id: 1, value: 'new' })
}
</script>
<!-- 부모 -->
<Child @update="onUpdate" />
slots
Vue3
부모가 자식 컴포넌트 내부에 마크업/콘텐츠를 주입할 수 있게 하는 기능.
<!-- Card.vue (자식) -->
<div class="card">
<slot name="header"></slot>
<slot></slot> <!-- 기본 슬롯 -->
</div>
<!-- 사용처 -->
<Card>
<template #header><h3>제목</h3></template>
<p>본문 내용</p>
</Card>
생명주기 훅
Vue3면접 빈출
import { onMounted, onUpdated, onUnmounted } from 'vue'
onMounted(() => {
// DOM이 화면에 붙은 직후 — API 호출, DOM 접근에 적합
})
onUpdated(() => { /* 반응형 데이터로 인해 DOM 갱신 후 */ })
onUnmounted(() => { /* 정리 작업 — 타이머/이벤트리스너 해제 */ })
| 훅 | 시점 |
onBeforeMount | 마운트 직전 |
onMounted | DOM 부착 완료 |
onBeforeUpdate | 리렌더 직전 |
onUpdated | 리렌더 완료 |
onBeforeUnmount | 제거 직전 |
onUnmounted | 제거 완료 |
컴포저블 (재사용 로직)
Vue3
Composition API 함수를 모듈화해 여러 컴포넌트에서 재사용. use 접두사 관례.
// composables/useCounter.js
import { ref } from 'vue'
export function useCounter(initial = 0) {
const count = ref(initial)
const increment = () => count.value++
return { count, increment }
}
// 컴포넌트에서 사용
import { useCounter } from '@/composables/useCounter'
const { count, increment } = useCounter(10)
Vue Router
Vue3
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{ path: '/', component: Home },
{ path: '/user/:id', component: UserView, props: true }
]
const router = createRouter({
history: createWebHistory(),
routes
})
// 네비게이션 가드
router.beforeEach((to, from) => {
if (to.meta.requiresAuth && !isLoggedIn()) {
return '/login'
}
})
<!-- 템플릿에서 -->
<router-link to="/about">소개</router-link>
<router-view />
Pinia (상태관리)
Vue3Vuex 대체
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
doubled: (state) => state.count * 2
},
actions: {
increment() { this.count++ }
}
})
// 컴포넌트에서
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
store.increment()
Vuex 대비 장점: 보일러플레이트 감소(mutations 불필요), TypeScript 친화적, devtools 통합, Composition API 스타일 작성(defineStore setup 함수형) 가능.
axios 연동 (Spring Boot API 호출)
Vue3
// api/client.js
import axios from 'axios'
const api = axios.create({
baseURL: '/api',
timeout: 5000
})
// 요청 인터셉터 — JWT 토큰 자동 첨부
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
export default api
// 컴포넌트에서 사용 (async/await + try-catch)
import { ref, onMounted } from 'vue'
import api from '@/api/client'
const users = ref([])
onMounted(async () => {
try {
const res = await api.get('/users')
users.value = res.data
} catch (e) {
console.error('조회 실패', e)
}
})
기본 문서 구조
HTML
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>페이지 제목</title>
</head>
<body>
...
</body>
</html>
DOCTYPE: 브라우저에게 표준 모드(standards mode)로 렌더링하라고 알리는 선언. 생략 시 quirks mode로 동작해 레이아웃이 깨질 수 있음.
시맨틱 태그
HTML면접 빈출
| 태그 | 의미 |
<header> | 머리글 영역 (로고, 네비게이션) |
<nav> | 주요 네비게이션 링크 모음 |
<main> | 문서의 핵심 콘텐츠 (페이지당 1개) |
<section> | 주제별로 묶인 콘텐츠 그룹 |
<article> | 독립적으로 배포 가능한 콘텐츠 (게시글, 카드) |
<aside> | 본문과 간접적으로 연관된 사이드 콘텐츠 |
<footer> | 바닥글 영역 |
<figure> / <figcaption> | 이미지·도표와 그 설명 캡션 묶음 |
<time> | 날짜/시간 (datetime 속성으로 기계 판독 가능 형식 제공) |
<mark> | 하이라이트(강조 표시) 텍스트 |
<details> / <summary> | JS 없이 열고 닫는 아코디언/접이식 콘텐츠 |
<dialog> | 네이티브 모달 다이얼로그 (showModal() 지원) |
<details>
<summary>자세히 보기</summary>
<p>JS 없이 펼쳐지는 내용</p>
</details>
왜 중요한가: 접근성(스크린리더), SEO, 코드 가독성 향상. <div> 남용보다 의미 있는 태그 사용이 면접에서도 자주 언급됨.
텍스트 태그
HTML
<h1>~<h6> // 제목 (문서당 h1 1개 권장, 계층 건너뛰지 말 것)
<p>문단</p>
<strong>중요(굵게+의미)</strong>
<em>강조(기울임+의미)</em>
<br> // 줄바꿈
<hr> // 수평 구분선
<span>인라인 묶음</span>
<blockquote cite="출처url">인용문</blockquote>
<code>코드 조각</code>
<pre>공백/줄바꿈 유지 텍스트</pre>
b/i vs strong/em: <b>, <i>는 시각적 스타일만 의미하지만 <strong>, <em>은 의미(중요도/강조)를 함께 전달해 스크린리더가 다르게 읽는다. 의미가 있다면 strong/em 사용.
목록 태그
HTML
<!-- 순서 없는 목록 -->
<ul>
<li>항목1</li>
<li>항목2</li>
</ul>
<!-- 순서 있는 목록 -->
<ol start="3">
<li>3번부터 시작</li>
</ol>
<!-- 정의 목록 -->
<dl>
<dt>HTML</dt>
<dd>웹 문서 마크업 언어</dd>
</dl>
테이블
HTML
<table>
<caption>월별 매출</caption>
<thead>
<tr><th scope="col">월</th><th scope="col">매출</th></tr>
</thead>
<tbody>
<tr><td>1월</td><td>1,200</td></tr>
<tr><td colspan="2">합계 행</td></tr>
</tbody>
<tfoot>
<tr><td>총계</td><td>1,200</td></tr>
</tfoot>
</table>
colspan / rowspan: 셀 병합 시 사용. scope="col"|"row"는 헤더 셀이 어느 축을 설명하는지 명시해 접근성을 높인다.
링크 / 이미지
HTML
<a href="https://example.com" target="_blank" rel="noopener noreferrer">
새 탭에서 열기
</a>
<img src="photo.jpg" alt="제품 사진 설명" width="400" loading="lazy" />
<!-- 반응형 이미지: 화면 크기별 다른 이미지 제공 -->
<picture>
<source media="(max-width: 600px)" srcset="small.jpg">
<img src="large.jpg" alt="설명">
</picture>
target="_blank" 보안 주의: 반드시 rel="noopener noreferrer"를 함께 써야 한다. 없으면 새로 열린 페이지가 window.opener를 통해 원본 페이지를 조작할 수 있는 보안 취약점(탭내빙) 발생.
alt 속성: 이미지가 로드되지 않거나 스크린리더 사용 시 대체 텍스트. 장식용 이미지는 alt=""로 비워 스크린리더가 건너뛰게 한다.
오디오 / 비디오
HTML
<video controls poster="thumb.jpg" width="640">
<source src="movie.mp4" type="video/mp4">
브라우저가 비디오를 지원하지 않습니다.
</video>
<audio controls autoplay loop muted>
<source src="song.mp3" type="audio/mpeg">
</audio>
autoplay 정책: 대부분 브라우저는 muted 없이 autoplay를 차단한다. 소리 있는 자동재생이 필요하면 사용자 상호작용 이후 JS로 play() 호출.
폼 기본 구조
HTML
<form @submit.prevent="onSubmit">
<fieldset>
<legend>회원 정보</legend>
<label for="email">이메일</label>
<input id="email" type="email" v-model="email" required />
<select v-model="role">
<optgroup label="내부">
<option value="admin">관리자</option>
</optgroup>
</select>
<textarea v-model="memo" rows="4"></textarea>
</fieldset>
<button type="submit">저장</button>
<button type="reset">초기화</button>
</form>
fieldset/legend: 관련 폼 요소를 묶고 그룹 제목을 제공해 접근성과 가독성을 높인다. 회원가입처럼 섹션이 나뉘는 폼에서 자주 사용.
input type 전체
HTML실무 필수
| type | 용도 |
text | 일반 한 줄 텍스트 |
password | 입력값 마스킹 처리 |
email | 이메일 형식 기본 검증 + 모바일 키보드 자동 전환 |
number | 숫자 입력, min/max/step 사용 가능 |
tel | 전화번호 (형식 검증은 없으나 모바일 숫자 키패드 표시) |
date / time / datetime-local | 네이티브 날짜/시간 선택기 |
checkbox | 다중 선택 |
radio | 단일 선택 (같은 name 그룹) |
file | 파일 업로드 (multiple, accept 속성 조합) |
range | 슬라이더 |
color | 색상 선택기 |
search | 검색창 (모바일에서 검색 아이콘 키보드) |
hidden | 화면에 안 보이지만 폼 전송에 포함되는 값 |
<input type="file" accept="image/*" multiple @change="onFileSelect" />
<input type="number" min="0" max="100" step="5" />
폼 검증 속성
HTML
<input
type="text"
required
minlength="2" maxlength="20"
pattern="[A-Za-z0-9]+"
placeholder="영문/숫자만 입력"
/>
| 속성 | 용도 |
required | 필수 입력 (빈 값이면 제출 차단 + 브라우저 기본 메시지) |
pattern | 정규식 기반 형식 검증 |
min / max | 숫자·날짜 범위 제한 |
minlength / maxlength | 문자 길이 제한 |
readonly | 값 수정 불가하지만 폼 전송에는 포함됨 |
disabled | 비활성화, 폼 전송에서 제외됨 |
readonly vs disabled: 면접에서 자주 묻는 차이. disabled는 값이 서버로 전송되지 않고 탭 포커스도 받지 않는다. readonly는 값을 못 바꾸지만 전송되고 포커스도 가능하다.
전역/중요 속성
HTML
| 속성 | 용도 |
id | 문서 내 고유 식별자 (CSS/JS 단일 대상) |
class | 스타일/스크립트용 그룹 식별자 (다수 적용 가능) |
data-* | 사용자 정의 데이터 속성 (JS에서 dataset으로 접근) |
aria-* | 접근성(스크린리더용) 속성 |
tabindex | 키보드 탭 이동 순서 제어 (0=자연순서, -1=탭 제외, 양수=비권장) |
loading="lazy" | 이미지/iframe 지연 로딩 |
contenteditable | 요소를 직접 편집 가능하게 만듦 |
draggable | 드래그 앤 드롭 활성화 |
hidden | 요소를 렌더링에서 제외 (display:none과 유사) |
title | 마우스 hover 시 툴팁 텍스트 |
<div data-user-id="42" data-role="admin"></div>
// JS: el.dataset.userId === '42'
메타 태그
HTML
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="페이지 설명 (검색결과 노출)">
<!-- 카카오톡/SNS 공유 미리보기 (Open Graph) -->
<meta property="og:title" content="제목">
<meta property="og:image" content="thumb.jpg">
<link rel="icon" href="/favicon.ico">
iframe / embed
HTML
<iframe
src="https://example.com"
width="600" height="400"
loading="lazy"
sandbox="allow-scripts allow-same-origin"
></iframe>
sandbox 속성: iframe 안 콘텐츠의 권한을 제한해 보안을 강화. 값을 비우면 모든 권한 차단, 필요한 권한만 명시적으로 허용하는 것이 안전.
웹 접근성 (ARIA)
HTML실무에서 점점 중요해짐
<!-- 버튼처럼 보이는 div에 역할/상태 부여 -->
<div role="button" tabindex="0" aria-pressed="false" @click="toggle">
토글
</div>
<!-- 스크린리더 전용 라벨 (시각적으로는 숨김) -->
<button aria-label="닫기">✕</button>
<!-- 동적으로 갱신되는 영역을 스크린리더에 알림 -->
<div aria-live="polite">{{ statusMessage }}</div>
| 속성 | 용도 |
role | 요소의 의미적 역할 명시 (button, dialog, alert 등) |
aria-label | 화면에 보이지 않는 접근성 라벨 제공 |
aria-hidden="true" | 장식용 요소를 스크린리더에서 숨김 |
aria-live | polite(대기 후 알림)/assertive(즉시 알림) — 동적 콘텐츠 변경 안내 |
aria-expanded | 아코디언/드롭다운의 펼침 상태 |
우선순위: 가능하면 시맨틱 태그(button, nav 등)를 먼저 쓰고, 부득이하게 div/span으로 인터랙션을 만들 때만 ARIA로 보완한다 (No ARIA is better than Bad ARIA).
SVG 기초
HTML
<svg width="100" height="100" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="#42b883" />
<rect x="10" y="10" width="30" height="30" />
</svg>
벡터 그래픽: 확대해도 깨지지 않아 아이콘/로고에 적합. CSS로 색상(fill/stroke) 제어가 가능해 다크모드 대응이 쉽다.
template / slot 태그
HTMLVue 연관
<!-- 네이티브 HTML template: 렌더링되지 않고 JS로 복제해 사용 -->
<template id="row-tpl">
<tr><td></td></tr>
</template>
Vue와의 관계: Vue 컴포넌트의 <template> 블록은 이 네이티브 태그를 차용한 것으로, 실제 DOM에는 렌더링되지 않고 컴파일 단계에서 가상 DOM으로 변환된다.
기본 문법 / 적용 방식
CSS
/* 선택자 { 속성: 값; } */
.title {
color: #333;
font-size: 18px;
}
| 적용 방식 | 예시 | 특징 |
| 외부 스타일시트 | <link rel="stylesheet" href="style.css"> | 유지보수 용이, 캐싱됨 (권장) |
| 내부 스타일시트 | <style>...</style> | 해당 문서에만 적용 |
| 인라인 스타일 | <div style="color:red"> | 명시도 가장 높음, 재사용 불가 (지양) |
선택자 전체
CSS면접 빈출
/* 기본 선택자 */
* { } /* 전체 선택자 */
p { } /* 태그 선택자 */
.card { } /* 클래스 선택자 */
#header { } /* id 선택자 */
/* 결합자 */
.card p { } /* 후손 선택자 (자손 전체) */
.card > p { } /* 자식 선택자 (직계만) */
h2 + p { } /* 인접 형제 (바로 다음) */
h2 ~ p { } /* 일반 형제 (이후 전체) */
/* 속성 선택자 */
input[type="text"] { }
a[href^="https"] { } /* https로 시작 */
a[href$=".pdf"] { } /* .pdf로 끝남 */
a[href*="naver"] { } /* naver 포함 */
/* 그룹 선택자 */
h1, h2, h3 { margin: 0; }
우선순위 (명시도)
CSS면접 빈출
동일 요소에 여러 규칙이 충돌할 때 적용 순서를 결정하는 점수 체계.
| 우선순위 | 점수 | 예시 |
| 1 (최고) | !important | color: red !important; |
| 2 | 인라인 스타일 | style="color:red" |
| 3 | id 선택자 | #header (0,1,0,0) |
| 4 | class/속성/가상클래스 | .card, [type], :hover (0,0,1,0) |
| 5 (최저) | 태그/가상요소 | div, ::before (0,0,0,1) |
!important 지양: 디버깅을 어렵게 만드는 주요 원인. 가급적 선택자 구조나 CSS 작성 순서로 우선순위를 해결하고, 라이브러리 스타일 강제 override 같은 예외적 상황에만 사용.
박스 모델
CSS면접 빈출
.box {
box-sizing: border-box; /* width에 padding+border 포함 */
width: 200px;
padding: 16px;
border: 1px solid #ccc;
margin: 8px;
}
content-box vs border-box: 기본값 content-box는 width가 콘텐츠 영역만 의미해 padding/border가 추가되어 실제 크기가 커짐. 실무에서는 거의 항상 border-box로 전역 설정.
/* 실무 관행: 전역 리셋 */
*, *::before, *::after { box-sizing: border-box; }
margin 상쇄(collapsing): 인접한 블록 요소의 위/아래 margin은 더 큰 값 하나로 합쳐진다(겹쳐짐). flex/grid 컨테이너 내부 아이템에는 발생하지 않는다.
display 속성
CSS면접 빈출
| 값 | 특징 |
block | 줄 전체 차지, width/height 적용 가능 (div, p, section) |
inline | 내용 크기만 차지, width/height 무시됨 (span, a) |
inline-block | 인라인처럼 배치되지만 width/height 적용 가능 |
none | 렌더링에서 완전히 제거 (공간도 차지 안 함) |
flex | 플렉스 컨테이너 — 1차원 레이아웃 |
grid | 그리드 컨테이너 — 2차원 레이아웃 |
inline-flex / inline-grid | 컨테이너 자체는 인라인처럼 배치 |
visibility:hidden vs display:none: 면접 단골. visibility:hidden은 안 보이지만 공간은 차지하고 이벤트도 안 받음. display:none은 레이아웃에서 완전히 제거됨. opacity:0은 보이지 않지만 공간 유지 + 클릭 이벤트는 여전히 발생.
Flexbox
CSS실무 필수
.container {
display: flex;
flex-direction: row; /* row | column | row-reverse | column-reverse */
justify-content: space-between; /* 주축 정렬 */
align-items: center; /* 교차축 정렬 */
flex-wrap: wrap; /* 줄바꿈 허용 */
gap: 12px;
}
.item {
flex: 1; /* flex-grow flex-shrink flex-basis 축약 */
flex-shrink: 0; /* 줄어들지 않음 */
order: 2; /* 시각적 순서 변경 (DOM 순서는 유지) */
align-self: flex-end; /* 개별 아이템만 교차축 정렬 재정의 */
}
| 속성 | 값 예시 |
| justify-content | flex-start / center / space-between / space-around / space-evenly |
| align-items | stretch / center / flex-start / flex-end / baseline |
| flex-direction | row / column / row-reverse |
flex: 1 의 의미: flex-grow:1; flex-shrink:1; flex-basis:0%;의 축약. 컨테이너의 남는 공간을 균등하게 나눠 가지라는 뜻.
Grid
CSS
.layout {
display: grid;
grid-template-columns: 240px 1fr; /* 사이드바 + 본문 */
grid-template-rows: auto 1fr auto;
grid-template-areas:
"sidebar header"
"sidebar content"
"sidebar footer";
gap: 16px;
}
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
/* 반응형 카드 그리드 (자동 줄바꿈) */
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}
Flexbox vs Grid: Flexbox는 1차원(행 또는 열) 정렬에 적합, Grid는 2차원(행+열) 레이아웃 설계에 적합. 페이지 전체 레이아웃은 Grid, 내부 컴포넌트 정렬은 Flexbox가 일반적 조합.
repeat(auto-fit, minmax()): 컨테이너 너비에 따라 컬럼 수가 자동으로 조절되는 반응형 그리드의 핵심 패턴. 태블릿 가로/세로 전환 시 매우 유용.
Position
CSS면접 빈출
| 값 | 동작 |
static | 기본값, 문서 흐름대로 배치 (top/left 등 무시) |
relative | 자기 원래 위치 기준 이동 (공간 유지) |
absolute | 가장 가까운 position:static이 아닌 부모 기준 |
fixed | 뷰포트 기준 고정 (스크롤해도 그대로) |
sticky | 스크롤 중 임계점까지 relative, 이후 fixed처럼 고정 |
.modal-wrap { position: relative; } /* 기준점 생성 */
.modal-close { position: absolute; top: 12px; right: 12px; }
.table-header { position: sticky; top: 0; } /* 스크롤시 헤더 고정 */
absolute 기준점: 부모 중 position이 static이 아닌(relative/absolute/fixed) 가장 가까운 요소를 찾는다. 없으면 body(뷰포트) 기준으로 동작 — 의도치 않은 위치 버그의 흔한 원인.
Float / Clear (레거시)
CSS
.thumbnail { float: left; margin-right: 12px; }
.clearfix::after { content: ""; display: block; clear: both; }
현재 위치: 과거엔 레이아웃 전체를 float로 잡았지만 현재는 Flexbox/Grid로 대체됨. 다만 텍스트가 이미지를 감싸는 "위키백과 스타일" 레이아웃에는 여전히 float가 쓰인다. 면접에서 "float를 언제 쓰나요" 질문에는 이 용도로 답하면 좋다.
단위 (rem / % / vw)
CSS
| 단위 | 기준 |
px | 절대 픽셀 |
rem | 루트(html) font-size 기준 — 반응형 디자인 권장 |
em | 부모 요소 font-size 기준 (중첩 시 누적됨에 주의) |
% | 부모 요소 크기 기준 |
vw / vh | 뷰포트 너비/높이의 1% |
vmin / vmax | 뷰포트 가로/세로 중 작은(큰) 쪽 기준 1% |
fr | Grid 전용, 남는 공간을 비율로 분배 |
실무 팁: 폰트 크기는 rem, 컴포넌트 내부 간격은 px 또는 rem, 전체 레이아웃 너비는 % 또는 fr을 섞어 쓰는 경우가 많다. 갤럭시탭처럼 화면이 다양한 환경에서는 vw 단독 사용보다 clamp() 조합을 권장.
색상 표기법
CSS
.box {
color: red; /* 키워드 */
color: #42b883; /* HEX */
color: rgb(66, 184, 131); /* RGB */
color: rgba(66, 184, 131, 0.5); /* RGB + 투명도 */
color: hsl(152, 47%, 49%); /* 색상/채도/명도 */
color: hsl(152 47% 49% / 0.5); /* HSL + 투명도 (최신 표기) */
}
HSL의 장점: 색상(hue)을 고정하고 명도/채도만 바꿔 톤온톤 팔레트를 만들기 쉬워 디자인 시스템 구축에 유리.
텍스트 / 폰트
CSS
.text {
font-family: 'Noto Sans KR', sans-serif;
font-size: 16px;
font-weight: 700; /* 100~900, normal=400, bold=700 */
line-height: 1.6; /* 단위 없는 값 = font-size의 배수 (권장) */
letter-spacing: -0.02em;
text-align: center;
text-decoration: underline;
text-transform: uppercase;
white-space: nowrap; /* 줄바꿈 방지 */
overflow: hidden;
text-overflow: ellipsis; /* 말줄임표(...) — white-space/overflow와 세트 */
}
말줄임표 3종 세트: white-space:nowrap; overflow:hidden; text-overflow:ellipsis; 세 줄이 항상 함께 다닌다. 카드 제목, 목록 텍스트 자르기에 실무에서 매우 자주 사용.
/* 여러 줄 말줄임 (line-clamp) */
.desc {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
배경 (background)
CSS
.hero {
background-color: #1b1f2a;
background-image: url('bg.jpg');
background-size: cover; /* 비율 유지하며 영역 꽉 채움 */
background-position: center;
background-repeat: no-repeat;
/* 축약형 */
background: #1b1f2a url('bg.jpg') no-repeat center / cover;
}
| background-size | 동작 |
cover | 비율 유지하며 영역을 꽉 채움 (일부 잘릴 수 있음) |
contain | 비율 유지하며 영역 안에 전부 보이게 (여백 생길 수 있음) |
100% 100% | 비율 무시하고 영역에 강제로 맞춤 |
테두리 / border-radius
CSS
.card {
border: 1px solid #2a2f3d;
border-radius: 12px;
border-top: 3px solid #42b883; /* 한쪽 면만 다르게 */
}
.avatar { border-radius: 50%; } /* 원형 */
.pill { border-radius: 999px; } /* 알약 모양 */
box-shadow / filter
CSS
/* box-shadow: x오프셋 y오프셋 blur spread color */
.card { box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
/* 안쪽 그림자 */
.inset { box-shadow: inset 0 0 8px rgba(0,0,0,0.3); }
/* filter: 이미지/요소 시각효과 */
.thumb { filter: blur(4px) grayscale(50%) brightness(1.1); }
.backdrop { backdrop-filter: blur(10px); } /* 뒤 배경에 적용 (유리효과) */
overflow / 스크롤
CSS
.scroll-box {
overflow: auto; /* 넘칠 때만 스크롤바 */
overflow-x: hidden;
overflow-y: scroll; /* 항상 스크롤바 표시 */
-webkit-overflow-scrolling: touch; /* iOS 관성 스크롤 */
}
| 값 | 동작 |
visible | 기본값, 넘친 콘텐츠가 영역 밖으로 그대로 보임 |
hidden | 넘친 부분 잘라서 숨김 |
scroll | 항상 스크롤바 표시 |
auto | 필요할 때만 스크롤바 표시 (실무에서 가장 흔히 사용) |
z-index / 쌓임 맥락
CSS면접 빈출
.modal { position: fixed; z-index: 1000; }
.dropdown { position: absolute; z-index: 10; }
z-index 함정: z-index는 position이 static이 아닌 요소에만 적용된다. 또한 부모가 새로운 "쌓임 맥락(stacking context)"을 만들면(예: transform, opacity<1, filter 적용) 자식의 z-index가 아무리 커도 부모 바깥의 다른 요소를 절대 넘어설 수 없다 — 실무에서 모달이 다른 요소 뒤에 숨는 버그의 단골 원인.
transform
CSS
.box {
transform: translate(10px, 20px); /* 이동 */
transform: scale(1.1); /* 확대/축소 */
transform: rotate(45deg); /* 회전 */
transform: skew(10deg); /* 기울이기 */
/* 여러 변형 조합 (공백으로 구분, 순서대로 적용) */
transform: translateX(-50%) rotate(10deg);
}
/* 정중앙 배치 패턴 (width/height 모를 때) */
.center {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
}
성능 이점: transform과 opacity는 GPU 가속을 받아 레이아웃 재계산(reflow) 없이 동작하므로, top/left로 애니메이션하는 것보다 훨씬 부드럽고 성능이 좋다.
transition
CSS실무 필수
.btn {
background: #2a2f3d;
transition: background 0.2s ease, transform 0.2s ease;
}
.btn:hover {
background: #42b883;
transform: translateY(-2px);
}
/* 축약형: property duration timing-function delay */
.fade { transition: all 0.3s ease-in-out 0.1s; }
| timing-function | 느낌 |
ease | 기본값, 천천히 시작-빠르게-천천히 끝 |
linear | 일정한 속도 |
ease-in | 천천히 시작해서 가속 |
ease-out | 빠르게 시작해서 감속 (UI 등장 효과에 자주 사용) |
animation / @keyframes
CSS
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.spinner {
animation: spin 1s linear infinite;
/* 축약: name duration timing-function delay iteration-count direction fill-mode */
}
@keyframes fadeInUp {
0% { opacity: 0; transform: translateY(10px); }
100% { opacity: 1; transform: translateY(0); }
}
.card { animation: fadeInUp 0.4s ease-out forwards; }
transition vs animation: transition은 상태 변화(hover 등) 시 한 번 보간하는 용도, animation은 @keyframes로 여러 단계를 정의하고 반복(infinite)도 가능한 좀 더 강력한 기능.
그라데이션
CSS
.linear { background: linear-gradient(135deg, #5b8def, #42b883); }
.radial { background: radial-gradient(circle, #fff, #ccc); }
/* 텍스트에 그라데이션 적용 */
.gradient-text {
background: linear-gradient(90deg, #5b8def, #42b883);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
가상 클래스 / 가상 요소
CSS면접 빈출
/* 가상 클래스(:) — 요소의 특정 '상태' */
.btn:hover { } /* 마우스 올림 */
.btn:active { } /* 클릭 중 */
.btn:focus { } /* 포커스됨 */
input:disabled { }
input:checked { }
li:first-child { }
li:last-child { }
li:nth-child(2n) { } /* 짝수번째 */
li:nth-child(odd) { } /* 홀수번째 */
p:not(.special) { } /* 제외 */
/* 가상 요소(::) — 요소 안에 '존재하지 않던 콘텐츠'를 생성 */
.box::before { content: "★ "; }
.box::after { content: ""; display: block; clear: both; }
.box::placeholder { color: #999; }
.box::selection { background: yellow; }
콜론 개수의 의미: 표준상 가상 클래스는 콜론 1개(:hover), 가상 요소는 콜론 2개(::before)로 구분한다 (단, 브라우저 호환을 위해 :before 1개도 동작은 함). 면접에서 "차이가 뭐냐"고 물으면 "클래스는 상태, 요소는 새 콘텐츠 생성"이라고 답하면 된다.
미디어 쿼리 (반응형)
CSS
/* 태블릿 이하 */
@media (max-width: 880px) {
.sidebar { display: none; }
}
/* 갤럭시 탭 가로/세로 전환 대응 */
@media (orientation: portrait) { }
@media (orientation: landscape) { }
/* 범위 동시 지정 */
@media (min-width: 600px) and (max-width: 1100px) {
.grid { grid-template-columns: repeat(2, 1fr); }
}
/* 다크모드 감지 */
@media (prefers-color-scheme: dark) { }
/* 호버 가능 여부 (터치 기기 구분에 유용) */
@media (hover: hover) { .btn:hover { } }
모바일 우선(Mobile First): min-width 기준으로 작은 화면부터 스타일을 작성하고 큰 화면에서 추가/override하는 방식이 실무 표준. max-width 기준(데스크톱 우선)도 가능하지만 우선순위 충돌을 줄이려면 한 방향으로 통일하는 것이 좋다.
컨테이너 쿼리
CSS최신 기능
뷰포트가 아닌 부모 컨테이너의 크기를 기준으로 스타일을 바꾸는 최신 CSS 기능. 사이드바 유무에 따라 같은 카드 컴포넌트가 다르게 보여야 할 때 유용.
.card-wrap { container-type: inline-size; container-name: card; }
@container card (min-width: 400px) {
.card { display: flex; flex-direction: row; }
}
미디어 쿼리와 차이: 미디어 쿼리는 전체 화면 크기 기준, 컨테이너 쿼리는 컴포넌트가 속한 부모 박스 크기 기준이라 재사용 가능한 컴포넌트 설계에 더 적합하다. Vue 컴포넌트 단위 개발과 궁합이 좋음.
CSS 변수 (Custom Properties)
CSS
:root {
--primary-color: #42b883;
--spacing-md: 16px;
}
.btn {
background: var(--primary-color);
padding: var(--spacing-md);
color: var(--text-color, #333); /* 두 번째 인자: fallback 값 */
}
/* 다크모드 전환 예시 */
[data-theme="dark"] {
--primary-color: #5b8def;
}
장점: 다크모드 전환, 테마 시스템, 디자인 토큰 관리에 유용. Sass 변수와 달리 런타임에 JS로 동적 변경 가능 (element.style.setProperty('--primary-color', '#000')).
최신 기능 (:has, clamp 등)
CSS최신 기능
/* :has() — 부모 선택자 (자식 상태에 따라 부모 스타일 변경) */
.form-group:has(input:invalid) { border-color: red; }
.card:has(img) { padding-top: 0; }
/* clamp(최소, 권장, 최대) — 반응형 값 자동 계산 */
h1 { font-size: clamp(20px, 4vw, 36px); }
/* min() / max() */
.container { width: min(90%, 1200px); }
/* aspect-ratio — 비율 고정 (썸네일 등) */
.thumb { aspect-ratio: 16 / 9; object-fit: cover; }
/* gap — flex에도 적용 가능 (예전엔 grid 전용) */
.flex-list { display: flex; gap: 8px; }
clamp(): 갤럭시탭처럼 화면 크기가 다양한 환경에서 미디어쿼리 없이도 부드럽게 크기가 조절되는 텍스트/여백을 만들 때 매우 유용하다.
초기화 / 리셋 CSS
CSS
/* 브라우저마다 다른 기본 스타일을 통일 */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
ul, ol { list-style: none; }
a { text-decoration: none; color: inherit; }
img { max-width: 100%; display: block; }
button, input, textarea { font: inherit; }
실무 관행: 직접 짜기보다 normalize.css나 modern-css-reset 같은 라이브러리를 사용하거나, Tailwind/UI 프레임워크 사용 시 자체 내장된 리셋을 그대로 활용하는 경우가 많다.
출근 첫날 준비 체크리스트
D-DAY
탭하면 체크/완료 표시되며, 새로고침 시 초기화됩니다 (오프라인 단독 파일이라 저장은 되지 않습니다).