🎨 Frontend CSS5. 색·시각 효과07 — Borders & Outlines (focus ring)

07 — Borders & Outlines (focus ring)

한 줄 답: border박스의 일부(레이아웃 차지)이고, outline박스 밖의 시각 효과(레이아웃 무관)다. 한 줄 차이 때문에 포커스 링은 outline의 영역이며, 모던 사실상 표준은 :focus-visible + outline-offset + outline-color 의 3중 조합이다. 그라데이션 border는 border-image 또는 gradient padding-box / conic border-box 트릭으로.


Why — 왜 border와 outline을 구분하나

표면적으로 비슷해 보이지만 본질이 다르다:

속성레이아웃모서리위치다중
border차지함 (박스 크기에 포함)border-radius 따름박스 안쪽side별 (top/right/bottom/left)
outline차지 안 함(대부분 브라우저) border-radius 따름 (2023~)박스 밖단일

이 차이가 결정적이다:

  • 포커스 링을 border로 만들면 → focus 시 2px border가 추가되어 옆 요소가 밀린다 (layout shift).
  • 포커스 링을 outline으로 만들면 → 박스 밖에 그려져 레이아웃 변동 없음.

이게 접근성 + 시각적 안정성의 갈림길이다.


How — 두 시스템의 비교


What — 5가지 패턴

1) Border 기본

.box {
  border: 2px solid oklch(70% 0 0);
  border-radius: 8px;
  border-top: 4px solid oklch(60% 0.2 254);  /* 한 변만 다르게 */
}
 
/* 개별 분해 */
.box {
  border-width: 2px;
  border-style: solid;
  border-color: oklch(70% 0 0);
}

border-style: none / hidden / dotted / dashed / solid / double / groove / ridge / inset / outset. dashed·dotted간격은 브라우저 구현체다 — 픽셀 단위 제어 불가.

2) Outline + Focus Ring (모던 표준)

/* 모든 인터랙티브 요소 */
button, a, input, [tabindex] {
  outline: 2px solid transparent;     /* 미리 둘러 두기 → 변동 없음 */
  outline-offset: 2px;
  transition: outline-color 0.15s;
}
 
button:focus-visible,
a:focus-visible,
input:focus-visible,
[tabindex]:focus-visible {
  outline-color: oklch(62% 0.21 254);
}

핵심 3가지:

  1. :focus-visible키보드 포커스만 표시. 마우스 클릭은 표시 안 함 (WCAG 2.4.7).
  2. outline-offset — 박스에서 떨어진 거리. :focus-visible과 항상 짝.
  3. outline-color: transparent로 미리 둘러 두기 — focus 시 색만 바뀌어 트랜지션 부드럽고 layout shift 없음.
/* 이중 outline 트릭 — 내부 흰 띠로 구분 */
button:focus-visible {
  outline: 2px solid oklch(62% 0.21 254);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px white, 0 0 0 6px oklch(62% 0.21 254);
}

3) Gradient Border — border-image

.gradient-border {
  border: 4px solid transparent;
  border-image: linear-gradient(in oklch, oklch(60% 0.2 254), oklch(60% 0.2 340)) 1;
}

border-image: <source> <slice>. 1이미지를 1:1로 매핑. 단점: border-radius가 적용되지 않는다 — 둥근 그라데이션 보더에는 다른 트릭 필요.

4) Gradient Border + Radius — background-clip 트릭

.gradient-rounded {
  border-radius: 12px;
  border: 4px solid transparent;
  background:
    linear-gradient(white, white) padding-box,
    linear-gradient(in oklch, oklch(60% 0.2 254), oklch(60% 0.2 340)) border-box;
}

두 배경 레이어:

  • 위: linear-gradient(white, white) padding-boxpadding 영역에 흰색 (내부)
  • 아래: linear-gradient(...) border-boxborder 영역까지 그라데이션 (테두리)

bordertransparent로 두고, border 영역의 배경이 보이게 만드는 트릭. border-radius 잘 동작.

5) Conic Gradient Border — 회전하는 테두리

.spinning-border {
  border-radius: 12px;
  border: 3px solid transparent;
  background:
    linear-gradient(var(--surface), var(--surface)) padding-box,
    conic-gradient(from 0deg, oklch(60% 0.2 254), oklch(60% 0.2 25), oklch(60% 0.2 145), oklch(60% 0.2 254)) border-box;
}
 
/* @property로 각도 transition */
@property --angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}
.spinning-border {
  --angle: 0deg;
  background:
    linear-gradient(var(--surface), var(--surface)) padding-box,
    conic-gradient(from var(--angle), ...) border-box;
  animation: spin 4s linear infinite;
}
@keyframes spin {
  to { --angle: 360deg; }
}

AI 모델 페이지·로딩 상태에 자주. 자세한 @property00-foundations/04-custom-properties.


What-if — 잘못 쓰면

1) outline: none 단독 — 접근성 차단

/* 절대 금지 */
button:focus { outline: none; }
*:focus { outline: 0; }

WCAG 2.4.7 (Focus Visible) 위반. 키보드 사용자는 지금 어디 포커스 있는지 모름. 대체 시각 단서를 반드시 제공:

/* 좋음 */
button { outline: 2px solid transparent; outline-offset: 2px; }
button:focus-visible { outline-color: oklch(62% 0.21 254); }

2) :focus만 쓰고 :focus-visible 무시

/* 옛 방식 — 마우스 클릭에도 outline 표시 */
button:focus { outline: 2px solid blue; }

문제: 마우스 클릭에도 outline이 떠서 어색. 모던 답:

button:focus-visible { outline: 2px solid blue; }

:focus-visible사용자가 키보드로 들어왔을 때만 매칭. UA가 최선의 추정을 한다 (키보드 Tab·shortcut·programmatic focus는 매칭, 마우스 클릭은 보통 매칭 안 함). Chrome 86+ / Safari 15.4+ / Firefox 85+.

3) border-radiusborder-image에 안 먹힘

/* radius 무시됨 */
.bad {
  border-radius: 12px;
  border: 4px solid;
  border-image: linear-gradient(red, blue) 1;
}

위 4번 트릭(background-clip: border-box)으로 해결. 또는 바깥 wrapper에 그라데이션 + 안쪽 자식에 흰 배경2 div 트릭.

4) outline-offset의 음수 — 안쪽 outline

.inset-focus:focus-visible {
  outline: 2px solid oklch(62% 0.21 254);
  outline-offset: -4px;  /* 박스 안쪽으로 들어옴 */
}

음수 offset은 박스 안에 outline을 그린다. 큰 카드의 focus 시 안쪽 테두리 효과. 단, border-radius와 함께 쓰면 모서리가 어색할 수 있다 (브라우저별 차이).

5) box-shadow로 outline 흉내 — 다중

/* 다중 outline은 box-shadow로만 가능 */
.fancy:focus-visible {
  box-shadow:
    0 0 0 2px white,
    0 0 0 4px oklch(62% 0.21 254),
    0 0 0 6px white,
    0 0 0 8px oklch(62% 0.21 25);
}

outline단일. 여러 겹 outline이 필요하면 box-shadow chain. 단, box-shadow는 layout 차지는 안 하지만 부모의 overflow: hidden에 잘린다 — outline은 그렇지 않다.

6) border-style: dashed불일치

.dashed {
  border: 2px dashed oklch(70% 0 0);
}

dashed·dotted점선 간격은 표준이 명시하지 않는다. Chrome·Safari·Firefox가 모두 다르게 그린다. 픽셀 단위 제어가 필요하면:

/* SVG 또는 background-image로 점선 */
.precise-dashed {
  background-image: linear-gradient(90deg, oklch(70% 0 0) 50%, transparent 50%);
  background-size: 8px 2px;
  background-position: bottom;
  background-repeat: repeat-x;
}

7) forced-colors 모드 호환

button:focus-visible {
  outline: 2px solid oklch(62% 0.21 254);
}
 
@media (forced-colors: active) {
  button:focus-visible {
    outline: 2px solid CanvasText;  /* 시스템 색 사용 */
  }
}

Windows 고대비 모드에선 작성자가 정의한 색이 무시된다. 시스템 키워드(CanvasText, Highlight, ButtonText)로 대응.


Insight — Outline의 부활

“outline은 한때 옛 속성이었다가 2020년대에 다시 1급 시민이 됐다”

CSS2(1998)에 outline이 도입됐을 때는 디버깅용에 가까웠다 — border와 비슷하지만 layout shift 없는 보조 도구. 2000년대 대부분의 디자이너가 outline: none으로 없애고 border로 포커스를 그렸다. 그 결과 포커스 시 layout shift가 만연했고, 접근성 평가가 늘 깨졌다.

2018년 Selectors Level 4에서 :focus-visible이 표준화되면서 분위기가 바뀌었다. “키보드일 때만 outline 표시” 가 가능해지자, 디자이너들이 outline을 받아들이기 시작했다. 2020년 Chrome이 :focus-visible을 기본 활성화, 2021년 Safari 15.4가 추가. 2023년부터 모든 브라우저가 outlineborder-radius를 따름 — 마지막 outline을 거부하던 시각적 이유가 사라졌다.

흥미로운 반전: 모던 outline은 macOS 시스템 포커스 링과 거의 동일한 동작이 됐다. macOS는 1990년대부터 button 밖 2~3px의 푸른 후광으로 포커스를 표시했다. 30년이 지나 CSS의 outline + offset + :focus-visible이 그 패턴을 따라잡았다.

또 하나의 반전: Tailwind v4는 기본 reset에서 outline: 2px solid transparent; outline-offset: 2px;를 모든 인터랙티브 요소에 깐다. 즉 layout shift 없는 미리-배치된 outline사실상 표준이 됐다. 이게 2024년 디자인 시스템의 기본 가정이다.

또 하나: border-image는 거의 죽은 속성이다. 2014년 표준화됐지만 border-radius와 안 맞아서 디자이너들이 거의 외면. 대신 background-clip: padding-box / border-box 2 layer 트릭이 사실상 표준이 됐다 — 표준이 늦게 따라온, 또 다른 사례.


요약 + Mermaid

  • border는 layout 차지, outline은 차지 안 함. 그래서 포커스 링은 outline.
  • 모던 표준 = :focus-visible + outline-offset + transparent outline 미리 깔기.
  • Gradient border는 background-clip: padding-box / border-box 2-layer 트릭 — border-imageborder-radius와 안 맞아 사실상 폐기.
  • 회전하는 conic border는 @property --angle + animation.
  • outline: none 단독은 WCAG 2.4.7 위반 — 항상 대체 시각 단서.
  • forced-colors에선 시스템 키워드(CanvasText).

챕터를 마치며

05-color-visual 챕터는 여기까지다. 색공간(01)에서 시작해 gradient(02) → shadow(03) → filter(04) → blend(05) → background(06) → border/outline(07) 의 7단계를 거쳤다.

이 챕터의 한 줄 결론: 2024년 이후의 시각 효과는 OKLCH를 기본 단위로, color-mix + :focus-visible + isolation을 기본 도구로 한다. 옛 도구(HSL, outline: none, border-image, attachment: fixed)는 대부분 모던 대안에 자리를 내줬다.

다음 챕터: 06-motion — 색이 공간의 합성이라면, 모션은 시간의 합성이다.