position: fixed,transition,::after가상 요소까지 —
지금까지 배운 CSS 개념을 총동원해서 실무형 사이드바 메뉴를 만들어봅니다!

[+], 닫힌 메뉴는 [-]로 표시됩니다<h1>to2.kr/br9</h1>
<!-- 사이드바 -->
<div class="side-bar">
<!-- 열림/닫힘 아이콘 -->
<div class="icon-status">
<div>▼</div> <!-- 닫힌 상태 아이콘 -->
<div>▶</div> <!-- 열린 상태 아이콘 -->
</div>
<!-- 중첩 메뉴 -->
<nav class="menu-box-1">
<ul>
<li>
<a href="#">1차 메뉴 아이템 1</a>
<ul>
<li><a href="#">2차 메뉴 아이템 1</a></li>
<li>
<a href="#">2차 메뉴 아이템 2</a>
<ul>
<li><a href="#">3차 메뉴 아이템 1</a></li>
<li><a href="#">3차 메뉴 아이템 2</a></li>
<li><a href="#">3차 메뉴 아이템 3</a></li>
</ul>
</li>
<li><a href="#">2차 메뉴 아이템 3</a></li>
</ul>
</li>
<li><a href="#">1차 메뉴 아이템 2</a></li>
<li><a href="#">1차 메뉴 아이템 3</a></li>
<li>
<a href="#">1차 메뉴 아이템 4</a>
<ul>
<li><a href="#">2차 메뉴 아이템 1</a></li>
<li><a href="#">2차 메뉴 아이템 2</a></li>
<li><a href="#">2차 메뉴 아이템 3</a></li>
<li>
<a href="#">2차 메뉴 아이템 4</a>
<ul>
<li><a href="#">3차 메뉴 아이템 1</a></li>
<li><a href="#">3차 메뉴 아이템 2</a></li>
<li><a href="#">3차 메뉴 아이템 3</a></li>
<li><a href="#">3차 메뉴 아이템 4</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</div>
<!-- 본문 -->
<div class="con">
Lorem ipsum dolor, sit amet consectetur...
</div>
ul (1차)
├── li > a "1차 메뉴 아이템 1"
│ └── ul (2차)
│ ├── li > a "2차 메뉴 아이템 1"
│ ├── li > a "2차 메뉴 아이템 2"
│ │ └── ul (3차)
│ │ ├── li > a "3차 메뉴 아이템 1"
│ │ └── ...
│ └── li > a "2차 메뉴 아이템 3"
├── li > a "1차 메뉴 아이템 2"
└── ...
/* 노멀라이즈 */
body, ul, li {
margin: 0;
padding: 0;
list-style: none;
}
a {
color: inherit;
text-decoration: none;
}
/* 사이드바 */
.side-bar {
position: fixed;
top: 0;
left: -270px;
width: 300px;
height: 100%;
background-color: #efefef;
transition: background-color .4s, left .4s, color .4s;
}
.side-bar:hover {
left: 0;
color: white;
}
.side-bar:hover, .side-bar > .menu-box-1 > ul ul {
background-color: black;
}
/* 아이콘 */
.side-bar > .icon-status > div {
text-align: right;
padding: 5px;
}
.side-bar > .icon-status > div:last-child {
display: none;
}
.side-bar:hover > .icon-status > div:last-child {
display: block;
}
.side-bar:hover > .icon-status > div:first-child {
display: none;
}
/* 메뉴 */
.side-bar > .menu-box-1 ul > li {
position: relative;
}
.side-bar > .menu-box-1 ul > li > a {
display: block;
font-weight: bold;
padding: 10px;
white-space: nowrap;
}
.side-bar > .menu-box-1 ul > li:hover > a {
background-color: white;
color: black;
}
/* [+] / [-] 아이콘 */
.side-bar > .menu-box-1 ul > li > a:not(:only-child)::after {
content: "[+]";
}
.side-bar > .menu-box-1 ul > li:hover > a:not(:only-child)::after {
content: "[-]";
}
/* 하위 메뉴 */
.side-bar > .menu-box-1 > ul ul {
display: none;
position: absolute;
left: 100%;
top: 0;
width: 200px;
}
.side-bar > .menu-box-1 ul > li:hover > ul {
display: block;
}
/* 본문 */
.con {
border: 10px solid red;
width: 800px;
margin-left: auto;
margin-right: auto;
}
body, ul, li {
margin: 0;
padding: 0;
list-style: none;
}
a {
color: inherit;
text-decoration: none;
}
💡 브라우저 기본 스타일 초기화. 모든 CSS 작업 전 가장 먼저 작성합니다.

.side-bar {
position: fixed; /* 스크롤해도 항상 같은 자리에 고정 */
top: 0;
left: -270px; /* 평소에는 화면 왼쪽 밖에 숨겨둠 */
width: 300px;
height: 100%;
background-color: #efefef;
/* 배경색, 위치, 글자색을 0.4초에 걸쳐 부드럽게 전환 */
transition: background-color .4s, left .4s, color .4s;
}
💡
left: -270px이 핵심입니다. 너비 300px 중 270px을 화면 밖으로 밀어두고, 30px만 살짝 보이게 해서 사이드바의 존재를 암시합니다.
화면 왼쪽 끝:
|-270px ← [사이드바 300px] | 30px만 보임
.side-bar:hover {
left: 0; /* 화면 안으로 들어옴 */
color: white; /* 글자색 흰색으로 변경 */
}
/* hover 시 배경색 black으로 변경 (2차 이하 메뉴도 함께) */
.side-bar:hover, .side-bar > .menu-box-1 > ul ul {
background-color: black;
}

💡
transition: left .4s가 적용되어 있어서left: -270px → 0이 0.4초 동안 부드럽게 전환됩니다.
hover 전: [사이드바] → 왼쪽에 숨어있음
hover 후: [사이드바] → 스르르 나타남 (0.4초)
/* 기본: ▼(닫힌 상태) 표시, ▶(열린 상태) 숨김 */
.side-bar > .icon-status > div:last-child {
display: none;
}
/* hover 시: ▶ 표시, ▼ 숨김 */
.side-bar:hover > .icon-status > div:last-child {
display: block;
}
.side-bar:hover > .icon-status > div:first-child {
display: none;
}
| 상태 | :first-child (▼) | :last-child (▶) |
|---|---|---|
| 기본 | 보임 | 숨김 |
| hover | 숨김 | 보임 |
/* 하위 메뉴의 기준점 설정 */
.side-bar > .menu-box-1 ul > li {
position: relative;
}
/* 링크를 block으로 — 클릭 영역 확장 */
.side-bar > .menu-box-1 ul > li > a {
display: block;
font-weight: bold;
padding: 10px;
white-space: nowrap; /* 텍스트 줄바꿈 방지 */
}
/* hover 시 해당 메뉴 아이템 강조 */
.side-bar > .menu-box-1 ul > li:hover > a {
background-color: white;
color: black;
}
💡
position: relative를li에 준 이유는, 하위ul을position: absolute로 배치할 때 기준점이 되기 위해서입니다!
/* 하위 메뉴가 있는 항목 뒤에 [+] 표시 */
.side-bar > .menu-box-1 ul > li > a:not(:only-child)::after {
content: "[+]";
}
/* hover 시 [-]로 변경 */
.side-bar > .menu-box-1 ul > li:hover > a:not(:only-child)::after {
content: "[-]";
}
💡
:not(:only-child)의 의미 — "유일한 자식이 아닌 경우" 즉, 형제 요소(하위 ul)가 있는 a 태그에만 적용합니다.
하위 메뉴 없음: 1차 메뉴 아이템 2 (::after 없음)
하위 메뉴 있음: 1차 메뉴 아이템 1 [+] (::after = "[+]")
hover 후: 1차 메뉴 아이템 1 [-] (::after = "[-]")
/* 기본: 하위 ul 숨김 */
.side-bar > .menu-box-1 > ul ul {
display: none;
position: absolute; /* li(relative) 기준으로 배치 */
left: 100%; /* 부모 li의 오른쪽에 붙임 */
top: 0;
width: 200px;
}
/* hover 시: 하위 ul 표시 */
.side-bar > .menu-box-1 ul > li:hover > ul {
display: block;
}

hover 전:
[1차 메뉴 아이템 1 [+]]
hover 후:
[1차 메뉴 아이템 1 [-]] [2차 메뉴 아이템 1]
[2차 메뉴 아이템 2 [+]]
[2차 메뉴 아이템 3]
↑
left: 100% → 오른쪽에 붙음
.con {
border: 10px solid red;
width: 800px;
margin-left: auto;
margin-right: auto;
}
| 개념 | 사용 위치 | 역할 |
|---|---|---|
position: fixed | .side-bar | 스크롤해도 고정 |
left: -270px | .side-bar | 화면 밖에 숨기기 |
transition | .side-bar | 부드러운 슬라이드 효과 |
position: relative | li | 하위 메뉴의 기준점 |
position: absolute | 하위 ul | li 오른쪽에 붙이기 |
display: none/block | 하위 ul, 아이콘 | hover 시 보이기/숨기기 |
::after | a:not(:only-child) | [+] / [-] 아이콘 |
:not(:only-child) | a | 하위 메뉴 있는 항목만 선택 |
white-space: nowrap | a | 텍스트 줄바꿈 방지 |