개발

견적서 인쇄에 CSS @page 여백 상자로 머리글·쪽 번호 넣기 — 크롬 131 이상에서만 되는 범위와 position: fixed 비교

  • 7th October 2026
  • 6 min read

견적서나 주문서, 증명서처럼 종이로 뽑히는 화면을 만들면 결국 같은 요청이 온다. 페이지마다 위에 회사 이름을 넣어 달라, 아래에 "2 / 5" 같은 쪽 번호를 넣어 달라.

웹 페이지는 원래 쪽이라는 개념이 없어서, 이걸 처리하는 방법은 오랫동안 둘 중 하나였다. 머리글을 position: fixed로 띄워서 인쇄할 때 매 쪽에 반복되게 하거나, 아예 서버에서 PDF 라이브러리로 문서를 만들어 내려주거나. 앞쪽은 쪽 번호를 못 넣고, 뒤쪽은 화면용 HTML과 인쇄용 문서를 따로 관리하게 된다.

CSS에는 이걸 위한 문법이 따로 있다. @page 안에 @top-left, @bottom-center 같은 페이지 여백 상자를 두면, 인쇄된 쪽의 여백 자리에 글자를 넣을 수 있다. 크롬이 2024년 11월 131에서 이걸 넣었다. 이 글은 크롬 154에서 PDF로 뽑아 확인한 결과와, 아직 크롬 계열에서만 된다는 점을 어떻게 다뤄야 하는지에 대한 것이다.

먼저 지원 범위: Baseline 인 것은 @page 의 절반이다

@page를 "2024년 12월 Baseline"으로 소개하는 글이 있는데, 둘을 나눠 봐야 한다. web-platform-dx의 Baseline 자료(webstatus.dev)에서 2024-12-11에 Baseline이 된 항목은 Page setup, 즉 @page의 size와 margin이다. 사파리 18.2가 마지막으로 들어오면서 채워졌다. 여백 상자는 이 항목에 들어 있지 않고, MDN 호환성 데이터(browser-compat-data 8.1.4, 2026-10-01)로 보면 이렇다.

기능크롬·엣지파이어폭스사파리
@page + size·margin159518.2
이름 붙인 페이지 (page 속성)85110지원
여백 상자 @top-left 등 16종131미지원미지원
string-set / string()미지원미지원미지원

여백 상자는 지금 크롬·엣지·삼성 인터넷 같은 크로미움 계열에서만 동작한다. 그러니 이건 "어디서나 쓰는 기능"이 아니라 "크롬에서 인쇄하면 머리글과 쪽 번호가 붙고, 다른 브라우저에서는 그것만 빠지는" 기능으로 써야 한다. 다행히 빠질 때 망가지는 것은 없다. 모르는 규칙은 무시되고 본문은 그대로 인쇄된다. 이 점은 뒤에서 다시 다룬다.

쪽 번호와 머리글 넣기

가상의 견적서 페이지에 이렇게 넣었다.

@page {
  size: A4;
  margin: 22mm 16mm 20mm;

  @top-left {
    content: "A업체 견적서";
    font: 600 10pt sans-serif;
    color: #1a4fa0;
    border-bottom: 1px solid #1a4fa0;
    vertical-align: bottom;
  }
  @bottom-center {
    content: counter(page) " / " counter(pages);
    font: 9pt sans-serif;
  }
  @bottom-right {
    content: "출력일 2026-10-07";
    font: 8pt sans-serif;
    color: #999;
  }
}

여백 상자는 이름 그대로 페이지 여백 안에 놓인다. margin을 22mm로 잡았으면 위쪽 상자의 높이가 22mm다. 위아래에 각각 왼쪽·가운데·오른쪽 셋, 좌우에 각각 위·가운데·아래 셋, 네 모서리까지 모두 16개 자리가 있다.

counter(page)는 현재 쪽, counter(pages)는 전체 쪽 수다. 따로 카운터를 선언할 필요가 없다. 상자 안에는 font, color, border, vertical-align이 그대로 먹었다. 위 예시의 파란 밑줄은 머리글 상자에 준 border-bottom이다.

A4 다섯 쪽으로 인쇄된 견적서. 첫 쪽에는 머리글이 없고 2쪽부터 왼쪽 위에 파란 머리글과 밑줄이 있다. 모든 쪽 아래에 1/5부터 5/5까지 쪽 번호와 출력일이 있고, 4쪽만 가로 방향이다.
크롬 154에서 PDF로 뽑은 다섯 쪽. 첫 쪽은 머리글 없음, 4쪽은 가로 방향

첫 쪽·짝수 쪽·특정 구간만 다르게

첫 쪽에는 문서 제목이 크게 있으니 머리글을 빼고 싶을 때가 많다. :first로 첫 쪽만 고른다. 양면 인쇄용으로 왼쪽·오른쪽 쪽을 다르게 하려면 :left, :right를 쓴다.

@page :first {
  @top-left { content: none; }
}

@page :left  { @bottom-left { content: "왼쪽 페이지"; } }
@page :right { @bottom-left { content: "오른쪽 페이지"; } }

뽑아 보니 1쪽이 오른쪽, 2쪽이 왼쪽으로 번갈아 붙었다. 책처럼 첫 쪽을 오른쪽에서 시작하는 규칙이다.

견적서 뒤에 열이 많은 단가표가 붙는다면 그 부분만 가로로 뽑을 수 있다. 이건 여백 상자가 아니라 이름 붙인 페이지 기능이고, 크롬·파이어폭스·사파리 모두 지원한다.

@page wide {
  size: A4 landscape;
  @top-left { content: "부록 — 가로 표"; }
}

.wide { page: wide; }

.wide 구간이 시작되면 새 쪽으로 넘어가고, 그 쪽만 A4 가로로 나온다. 위 그림의 4쪽이 그렇게 나왔고, 머리글도 이름 붙인 페이지에 준 문구로 바뀌었다. PDF의 쪽 크기를 직접 읽어 보면 4쪽만 841.92 × 594.96pt, 나머지는 594.96 × 841.92pt였다.

position: fixed 로 하던 방법과 비교

기존 방법을 같은 조건으로 뽑아 봤다.

@media print {
  header { position: fixed; top: 0; left: 0; right: 0; }
  footer { position: fixed; bottom: 0; left: 0; right: 0; }
  footer::after { content: "쪽 " counter(page); }
}
position fixed 방식으로 인쇄한 2쪽의 위와 아래. 머리글 바로 밑에 품목 줄 42가 첫 줄로 보이고 41번 줄은 머리글에 가려 안 보인다. 아래에는 쪽 0이라고 찍혀 있다.
position: fixed — 2쪽의 윗부분과 아랫부분
여백 상자 방식으로 인쇄한 2쪽의 위와 아래. 머리글은 여백 안에 있고 본문과 겹치지 않는다. 아래에는 왼쪽 페이지, 2 / 5, 출력일이 있다.
여백 상자 — 2쪽의 윗부분과 아랫부분

차이가 둘 있었다.

  • 본문이 머리글 밑에 깔린다. 2쪽은 "품목 줄 41"부터 시작해야 하는데 첫 줄이 42로 보인다. 41번 줄은 머리글 뒤에 가려졌다. fixed 요소는 본문 흐름과 상관없이 쪽마다 같은 자리에 그려질 뿐이라, 본문이 그 자리를 비켜 주지 않는다.
  • 쪽 번호가 0이다. 일반 요소 안에서 counter(page)는 값이 없다. 쪽 번호는 여백 상자 안에서만 의미가 있다.

여백 상자는 본문 영역 바깥, 여백 안에 놓이기 때문에 겹칠 일이 없다. 본문은 @page의 margin이 남긴 자리 안에서만 흐른다.

브라우저 기본 머리글·바닥글과 겹칠 때

크롬 인쇄 대화상자에는 "머리글과 바닥글" 옵션이 있다. 켜면 위에 날짜와 문서 제목, 아래에 주소와 쪽 번호가 찍힌다. 여백 상자를 넣은 문서에서 이 옵션을 켜면 어떻게 되는지 쟀다. 헤드리스 크롬에서 같은 옵션(displayHeaderFooter)을 켜고 PDF를 뽑았다.

문서 상태위쪽아래쪽
여백 상자 없음브라우저 날짜·제목브라우저 주소·쪽 번호
아래쪽에만 @bottom-center브라우저 날짜·제목내 쪽 번호만
위·아래 모두 있음내 머리글만내 바닥글만
:first에서 위쪽 상자를 content: none브라우저 날짜·제목이 다시 나옴내 바닥글
:first에서 위쪽 상자를 content: ""비어 있음내 바닥글

위쪽과 아래쪽이 따로 판단됐다. 그 쪽의 위 여백에 내 상자가 하나라도 있으면 브라우저 머리글이 빠지고, 하나도 없으면 브라우저 머리글이 들어온다. 그래서 첫 쪽 머리글을 none으로 지웠더니, 사용자가 옵션을 켜 둔 경우 그 자리에 브라우저의 날짜와 제목이 대신 찍혔다. 첫 쪽 위를 정말 비우고 싶다면 빈 문자열을 준다.

@page :first {
  @top-left { content: ""; }   /* none 이 아니라 빈 문자열 */
}

none은 상자를 아예 만들지 않고, ""는 내용이 빈 상자를 만든다. 브라우저 입장에서는 뒤쪽이 "작성자가 이 자리를 쓰고 있다"로 보인다.

장 제목을 머리글에 넣는 string-set 은 아직 안 된다

명세(CSS Generated Content for Paged Media)에는 본문의 제목을 읽어 머리글에 넣는 문법도 있다.

h2 { string-set: chapter content(); }

@page {
  @top-right { content: string(chapter); }
}

크롬 154에서 CSS.supports('string-set', 'chapter content()')는 false였고, 뽑은 PDF의 오른쪽 위 상자도 비어 있었다. MDN 호환성 데이터에는 이 속성 항목 자체가 없다. 장마다 머리글이 바뀌어야 한다면 지금은 장마다 이름 붙인 페이지를 하나씩 만들어 머리글 문구를 따로 주는 수밖에 없다.

다른 브라우저에서는 어떻게 되나

여백 상자를 모르는 브라우저에서는 @top-left 같은 규칙만 무시된다. size와 margin은 모든 주요 브라우저가 이해하니, 쪽 크기와 여백은 어디서나 같고 머리글·쪽 번호만 빠진다. 그래서 이렇게 나눠 생각하면 된다.

  • 머리글·쪽 번호가 있으면 좋은 문서 — 화면에서 인쇄 버튼을 눌러 뽑는 견적서, 주문 내역 같은 것. 여백 상자를 그냥 넣는다. 크롬 사용자는 쪽 번호가 붙은 문서를 받고, 나머지는 지금과 같다.
  • 머리글·쪽 번호가 반드시 있어야 하는 문서 — 제출용 증명서, 계약서처럼 쪽 번호가 빠지면 안 되는 것. 브라우저 인쇄에 맡기지 말고 서버에서 PDF로 만들어 내려주는 편이 맞다. 크롬 헤드리스로 PDF를 만들고 있다면 같은 CSS가 그대로 먹으니 인쇄용 HTML 하나로 화면 인쇄와 PDF를 같이 쓸 수 있다.

파이어폭스와 사파리에서 직접 뽑아 보지는 못했다. 위 표의 지원 여부는 MDN 호환성 데이터 기준이다.

정리

  • 2024년 12월 Baseline이 된 것은 @page의 size·margin이다. 머리글·쪽 번호를 넣는 여백 상자는 크롬 131부터 크로미움 계열에서만 된다.
  • @bottom-center { content: counter(page) " / " counter(pages); } 한 줄이면 쪽 번호가 붙는다. 일반 요소 안의 counter(page)는 0이다.
  • position: fixed 머리글은 다음 쪽부터 본문 첫 줄을 가렸다. 여백 상자는 여백 안에 놓여 겹치지 않는다.
  • 첫 쪽은 :first, 양면은 :left/:right, 특정 구간의 가로 방향은 이름 붙인 페이지로 고른다.
  • 브라우저 기본 머리글은 위·아래를 따로 판단한다. 첫 쪽 위를 비울 때는 none이 아니라 ""를 준다.
  • 모르는 브라우저에서는 머리글·쪽 번호만 빠지고 본문은 그대로다.

함께 읽으면 좋은 글