한 줄 요약 — 의사 요소 ::before::after는 HTML에 태그를 더 쓰지 않고도 한 요소의 본문 (::before)과 (::after)에 장식을 자동으로 붙이는 CSS다. 단, content 속성이 없으면 끈을 매달 자리는 있어도 끈 자체가 없는 셈이라 아무것도 보이지 않는다 — 빈 장식이라도 content: ""가 반드시 있어야 나타난다. content에는 글자뿐 아니라 따옴표, data-* 값(attr()), 자동 번호(counter)까지 담을 수 있다.

학습 목표#

  • ::before::after가 각각 본문 앞·뒤에 붙는다는 것을 알고 구분할 수 있다.
  • content 속성이 의사 요소의 필수 조건임을 이해하고 빈 값까지 줄 수 있다.
  • content에 글자·따옴표·attr() 값을 담을 수 있다.
  • counter로 목록 번호를 직접 적지 않고 자동으로 매길 수 있다.
  • 장식용 의사 요소가 스크린리더에 잘못 읽히지 않게 다룰 수 있다.

오늘의 비유 — 본문 앞뒤에 자동으로 매다는 작은 장식 끈#

잘 꾸민 책의 한 단락을 떠올려 보자. 단락의 글자(본문)는 그대로 두고, 시작 지점에 작은 장식 끈을, 끝 지점에 또 하나의 끈을 매달아 둔 모습이다. 이 끈은 책에 인쇄된 글이 아니다. 글자를 한 자도 고치지 않고 나중에 매단 장식일 뿐이라, 책 내용을 그대로 옮겨 적으면 끈은 따라오지 않는다.

::before는 본문 에 매단 끈, ::after는 본문 에 매단 끈이다. 둘 다 HTML 태그가 아니라 CSS가 만들어 붙이는 가짜 요소라서 "의사(pseudo, 흉내 낸) 요소"라고 부른다. 중요한 건 끈에 무엇이 적혀 있는지다. 끈을 매달 고리(::before)만 만들고 정작 끈에 아무것도 안 달면(=content 없음) 화면엔 아무 일도 일어나지 않는다. 그래서 의사 요소는 늘 "어느 쪽 끈인가(::before/::after)"와 "끈에 뭘 다는가(content)"를 한 쌍으로 생각한다.

핵심 개념#

::before와 ::after — 본문 앞과 뒤, 두 개의 끈#

같은 요소에 끈을 두 개까지 맬 수 있다. 앞쪽은 ::before, 뒤쪽은 ::after다. 둘은 붙는 위치만 다를 뿐 쓰는 법은 똑같다.

.note::before {
  content: "📌 ";   /* 본문 앞에 붙는 끈 */
}

.note::after {
  content: " ←";    /* 본문 뒤에 붙는 끈 */
}

<p class="note">저장 잊지 마세요</p>라고만 적어도, 화면에는 📌 저장 잊지 마세요 ←처럼 앞뒤로 장식이 붙는다. HTML에는 글자만 있고, 앞뒤 끈은 CSS가 매단 것이다. 끈은 기본적으로 인라인(글자처럼 한 줄에 흐름)으로 생기므로, 박스처럼 크기를 주고 싶으면 지난 회차에서 본 display: inline-block을 따로 줘야 한다.

content가 없으면 끈도 없다#

의사 요소의 가장 큰 함정이자 가장 중요한 규칙이다. content 속성이 아예 없으면 의사 요소는 만들어지지조차 않는다. 고리는 걸었는데 끈을 안 단 셈이라, 색을 칠하든 크기를 주든 화면엔 아무것도 안 보인다.

/* (content가 없어 의사 요소 자체가 생기지 않습니다 — 아래 색·크기는 무용지물입니다) */
.line::before {
  width: 40px;
  height: 2px;
  background: #1f2937;
}

장식용 막대·점선처럼 글자가 필요 없는 경우라도, 빈 끈을 뜻하는 content: ""를 반드시 줘야 한다. 그제야 의사 요소가 생기고 크기·배경이 먹는다.

.line::before {
  content: "";        /* 빈 끈이라도 있어야 나타난다 */
  display: inline-block;
  width: 40px;
  height: 2px;
  background: #1f2937;
}

content에 담을 수 있는 것 — 글자, 따옴표, attr()#

끈에 적을 수 있는 건 평범한 글자만이 아니다. 자주 쓰는 세 가지를 보자.

/* 1) 그냥 글자 */
.tip::before { content: "TIP "; }

/* 2) 따옴표 — 인용구 앞뒤에 큰따옴표를 자동으로 */
blockquote::before { content: "\201C"; }   /* “ */
blockquote::after  { content: "\201D"; }   /* ” */

/* 3) attr() — HTML의 data-* 값을 그대로 끌어와 끈에 출력 */
.badge::after { content: " (" attr(data-count) ")"; }

세 번째 attr()가 특히 쓸모 있다. <span class="badge" data-count="3">알림</span>이라고만 적으면 화면엔 알림 (3)이 나온다. 숫자 3을 CSS에 박지 않고 HTML 속성에서 가져오므로, 값이 바뀌어도 HTML의 data-count만 고치면 된다. 끈에 인쇄될 내용을 본문 쪽에서 정해 주는 셈이다.

counter — 번호를 자동으로 매기는 끈#

목록 항목에 1., 2.처럼 번호를 손으로 적지 않고 CSS가 세어 붙이게 할 수 있다. 부모에서 counter-reset으로 계수기를 0으로 두고, 각 항목에서 counter-increment로 하나씩 올린 뒤 그 값을 content에 출력한다.

.steps { counter-reset: step; }      /* 계수기를 0으로 시작 */

.steps li::before {
  counter-increment: step;           /* 항목마다 1씩 증가 */
  content: counter(step) ". ";       /* 그 숫자를 앞 끈에 출력 */
  font-weight: 700;
}

항목을 중간에 추가하거나 순서를 바꿔도 번호가 자동으로 다시 매겨진다. ol의 기본 번호로 충분할 때는 굳이 쓸 필요가 없지만, 1단계·Q1 같은 꾸민 번호가 필요할 때 유용하다.

함께 따라하기 — 체크 표시 끈과 라벨 뱃지#

::before로 항목 앞에 초록 체크 표시를 붙이고, ::afterattr()로 빈 칸을 라벨 뱃지로 바꿔 본다.

<ul class="checklist">
  <li>의사 요소 이해하기</li>
  <li>content 속성 쓰기</li>
</ul>

<span class="tag" data-label="HTML"></span>
.checklist li {
  list-style: none;          /* 기본 점 제거 */
}

.checklist li::before {
  content: "✓ ";             /* 앞에 붙는 체크 끈 */
  color: #16a34a;
  font-weight: 700;
}

.tag::after {
  content: attr(data-label); /* data-label 값을 라벨로 */
  padding: 2px 10px;
  border-radius: 999px;
  background: #1f2937;
  color: #fff;
  font-size: 0.8rem;
}

저장하고 열어 보면, 목록의 각 줄 앞에 초록색 가 붙어 체크리스트처럼 보인다. 그리고 아무 글자도 없던 빈 spanHTML이라고 적힌 짙은 알약 모양 뱃지로 바뀐다 — 그 글자는 HTML 본문이 아니라 data-label 값을 ::after가 끌어와 매단 끈이다. data-label="CSS"로 바꾸면 뱃지 글자도 따라 바뀐다.

흔한 실수 3가지#

1. content 속성을 빼면 의사 요소가 안 보인다#

가장 흔한 막힘이다. ::before에 색·크기를 다 줬는데 화면엔 아무것도 없다.

/* (content가 없어 끈 자체가 생기지 않습니다) */
.divider::before {
  display: inline-block;
  width: 60px;
  height: 2px;
  background: #999;
}

의사 요소는 content가 있어야 비로소 존재한다. 장식용 막대처럼 글자가 필요 없어도 빈 끈인 content: ""를 반드시 넣는다. 이 한 줄이 빠져서 안 보이는 경우가 정말 많으니, "의사 요소가 안 보인다"면 content부터 확인한다.

.divider::before {
  content: "";        /* 이 줄이 있어야 나타난다 */
  display: inline-block;
  width: 60px;
  height: 2px;
  background: #999;
}

2. 의사 요소를 진짜 img처럼 다루려고 한다#

content: url(...)로 이미지를 넣을 수는 있다. 그래서 로고나 사진 같은 의미 있는 이미지까지 의사 요소로 넣으려는 시도가 나온다.

/* (의미 있는 이미지를 끈에 매달면 alt를 줄 수 없습니다) */
.logo::before {
  content: url(/logo.png);
}

문제는 끈에 매단 이미지에는 alt를 줄 수 없다는 점이다. 의사 요소는 어디까지나 장식이고, 본문이 아니다. 로고·제품 사진처럼 내용을 전달하는 이미지는 지난 강의에서 배운 진짜 <img> 태그로 넣고 alt를 적는다. 의사 요소는 점·선·따옴표·작은 아이콘 같은 곁다리 장식에만 쓴다.

<img src="/logo.png" alt="Life is One Coin 로고" />

3. 스크린리더가 읽으면 안 되는 장식을 content로 넣는다#

content에 넣은 글자는 일부 스크린리더가 그대로 읽는다. 그래서 정보를 오직 의사 요소로만 전달하면 위험하다.

/* (이 별표가 "필수"라는 정보를 의사 요소로만 전달하고 있습니다) */
.field-label::after {
  content: " *";
  color: #dc2626;
}

색만으로 정보를 주지 않는다는 원칙(지난 접근성 메모)과 같은 맥락이다. "필수"라는 사실이 빨간 별표 하나에만 걸려 있으면, 그 별표를 못 읽는 환경에선 정보가 사라진다. 의미가 있는 내용은 HTML 본문에 글자로 두고, 의사 요소는 순수 장식으로만 쓴다. 별표를 시각 장식으로 남기더라도, 본문에 "(필수)" 같은 글자를 함께 두는 식이다.

<label class="field-label">이메일 <span class="req">(필수)</span></label>

오늘 배운 것 체크리스트#

  • ::before는 본문 앞, ::after는 본문 뒤에 붙는다.
  • content가 없으면 의사 요소는 아예 생기지 않는다 — 빈 장식도 content: "".
  • content에 글자·따옴표·attr() 값·counter를 담을 수 있다.
  • attr()data-* 값을, counter로 자동 번호를 끌어온다.
  • 의미 있는 이미지·정보는 본문에 두고, 의사 요소는 장식으로만 쓴다.

자주 묻는 질문#

Q. 인용구 앞뒤에 따옴표를 자동으로 넣으려면 어떻게 하나요?

A. blockquote::beforeblockquote::aftercontent에 따옴표 문자를 주면 됩니다. 곡선 따옴표는 유니코드로 content: "\201C"(여는 큰따옴표 “), content: "\201D"(닫는 큰따옴표 ”)처럼 적습니다. 인용문마다 따옴표를 직접 타이핑하지 않아도, blockquote로 감싸기만 하면 CSS가 앞뒤에 따옴표 끈을 매답니다.

Q. 목록 번호를 직접 안 쓰고 CSS로 자동으로 매길 수 있나요?

A. counter를 씁니다. 부모에 counter-reset: 이름으로 계수기를 만들고, 각 항목의 ::before에서 counter-increment: 이름으로 1씩 올린 뒤 content: counter(이름) ". "로 출력합니다. 항목을 추가하거나 순서를 바꿔도 번호가 자동으로 다시 매겨져, 1단계·Q1처럼 꾸민 번호를 만들 때 편합니다.

Q. contentattr()로 가져온 값은 어디서 정하나요?

A. HTML 요소의 속성에서 가져옵니다. content: attr(data-count)라고 쓰면 그 요소의 data-count 속성 값이 그대로 끈에 출력됩니다. 값을 바꾸고 싶으면 CSS가 아니라 HTML의 data-* 속성만 고치면 되고, 표준 속성(예: attr(title))도 같은 방식으로 끌어올 수 있습니다.

다음 시간 예고#

내일은 Transform — translate, rotate, scale, skew를 한 번에를 다룬다. 요소를 자리에서 떼지 않고 옮기고·돌리고·키우는 변형을, 위치 속성보다 가볍게 처리하는 법을 짚는다.

더 알아보기#