Red Hat
검정 OpenShift 머리띠 아래, 파드 상태 링을 두른 배포 노드와 연결 화살표·오른쪽 상세 서랍
Brand DNA
브랜드 개요
Red Hat의 제품 화면은 검은 OpenShift 머리띠 아래, 자원 사이의 관계와 상태를 읽는 작업 공간이다. 붉은 모자는 제품의 출처를 알리고, 파란 링크와 선택선은 작업할 위치를 알린다. 마케팅의 큰 빨간 면을 관리 콘솔 전체에 옮기지 않는다. 관리자가 오래 보는 흰색·회색 바탕, 개발자가 살펴보는 배포 노드, 운영자가 확인하는 상태 표시가 이 문서의 중심이다.
- 브랜드: Red Hat. 오픈소스 소프트웨어와 기업용 인프라 제품을 제공한다. 이 룩북은 공개 디자인 시스템 PatternFly 6와 OpenShift 웹 콘솔을 함께 다룬다.
- 시그니처: 검정 머리띠의 붉은 모자, Red Hat Display 제목, 둥근 배포 노드를 감싸는 파드 상태 링, 노드 사이의 화살표, 선택한 자원의 오른쪽 상세 서랍.
- 사용 환경: 개발자의 Topology, 운영자의 Cluster Overview, 자원 목록과 상세 화면. 홍보용 랜딩 페이지나 제품 소개 자료의 서체 크기와 여백을 관리 콘솔에 그대로 적용하지 않는다.
- 문서 범위: 색·간격·글자·모서리·동작 시간은 PatternFly v6.4.0 태그의 원본 토큰을 기준으로 고정했다. 문서 사이트의 최신 예시와 버전이 달라도 이 문서 안에서는 한 버전의 값으로 비교할 수 있다.
자료와 예시의 범위. PatternFly는 2014년 5월 16일 공개됐으며, PF6 시각 개정은 2024년 10월 28일 발표됐다(출처: PatternFly 공식 생일 글·PF6 소개 글). 토큰은 v6.4.0에 고정한다. Developer perspective의 검정 머리띠·노드·서랍과 PF6 컴포넌트를 결합한 정적 예시다. OpenShift 4.19의 기본 화면은 통합 콘솔이며, 사용자 설정에서 Developer perspective를 활성화할 수 있다. 아래 축약 치수·가상 데이터·재구성 SVG는 (추정)이고 공식 토큰 값과 구분한다.
노드 안의 브라우저·큐브·작업 기어·캐시 스택·데이터베이스는 서로 다른 가상 런타임 그림이다. 실제 다른 기업의 로고를 넣지 않고도 자원의 종류를 구분하도록 만들었다. 노드 이름, 프로젝트 이름, 시간과 사용량은 설명용 예시다. UI에서 쓰는 Deployment, StatefulSet, Pods, Resources, Observe 등의 용어와 수치 단위는 실제 콘솔 문법을 따른다. 상태 링의 파랑과 선택선의 파랑은 역할이 다르므로 클래스와 토큰도 나눈다.
라이트·다크를 모두 지원한다. 공식 dark 토큰과 OpenShift의 Light / Dark / System Default 설정을 따른다. 별도 다크 문서의 :root에도 다크 값을 직접 두어 카드·차트·표·상태가 함께 전환된다.
Tone & Mood
디자인 성격
핵심 키워드: 명료함 · 연결성 · 절제. 운영자는 경고의 원인과 영향 범위를 빠르게 찾아야 한다. 배경을 복잡하게 장식하기보다 상태, 이름, 관계, 다음 행동을 순서대로 읽게 한다. 화면은 빽빽하지만 정보의 종류마다 같은 위치와 같은 표시법을 쓴다. 같은 정렬을 유지하면 자원 수가 늘어나도 새 화면을 다시 배우는 부담이 줄어든다.
| 축 | 판단 | 화면에 적용하는 방식 | |---|---|---| | 색조 | 무채색 중심, 빨강·파랑의 역할 분리 | 검정 머리띠의 모자만 Red Hat Red, 링크·선택·주요 행동은 PatternFly Blue | | 밀도 | Compact | Overview에 세 열을 놓고 카드 안에는 짧은 제목, 수치, 단위, 상태를 함께 배치 | | 모서리 | Round | PF6 카드 16px, 입력 6px, 버튼 999px. 모서리가 다른 이유를 기능으로 설명 | | 입체감 | Subtle | 카드는 약한 그림자, 열린 서랍은 가장자리 그림자. 차트 바탕은 평평하게 유지 | | 서체 | Sans-serif | Display는 제목, Text는 자원 설명과 표, Mono는 이름·주소·명령 등 식별 문자열 | | 화면 언어 | 영어 | Overview, Topology, Details, Resources, Observe와 같은 실제 제품 용어 사용 |
문맥은 세 위치에 나눈다. 검정 머리띠에 제품 로고·클러스터·알림, 왼쪽 탐색 위에 Developer 선택, 본문 위에 Project 선택을 둔다. 카드의 storefront 프로젝트와 ⑫의 prod-east-01 클러스터를 먼저 읽고 그 범위 안의 자원을 살핀다.
중앙 그래프는 관계를 보여준다. 상태가 정상인 노드를 모두 초록으로 칠하지 않는다. 파드 링은 실행 상태, 작은 체크 배지는 빌드 상태, 종류 배지는 리소스 종류를 알린다. 연결선은 서비스의 호출 방향을 나타내고 묶음 테두리는 애플리케이션 범위를 나타낸다. 이 네 가지 표시가 같은 색이나 같은 외곽선으로 합쳐지지 않게 한다. 복잡한 그림을 더하는 목적은 장식이 아니라 구분이다.
선택 상태는 면 전체보다 경계와 제목으로 알린다. 왼쪽 메뉴의 세로 표시, 탭의 아래 선, 노드 바깥의 얇은 파란 원은 각각 현재 위치를 나타낸다. 경고 색을 선택 상태에 쓰면 사용자가 장애로 오해할 수 있다. 다크에서도 선택선의 명도를 높이고, 바탕은 회색 표면 위계로 남긴다. 검정·진회색·중간 회색의 세 단계만으로도 메뉴와 작업 영역과 뜬 패널을 분리할 수 있다.
큰 숫자는 의미 있는 곳에만 쓴다. CPU 비율에는 작은 cores 수치를 붙이고, Memory에는 GiB, Network에는 MB/s를 붙인다. 버전 4.19.8이나 포트 8080은 KPI처럼 키우지 않는다. Inventory의 Nodes·Pods·PVCs는 클릭해서 목록으로 내려가는 개수이므로 같은 크기로 맞춘다. 가장 큰 글자가 항상 가장 큰 자원을 뜻하지 않도록 정보의 종류와 강조의 이유를 분명히 한다.
Color System
컬러 시스템
Red Hat Red는 #EE0000이고, PatternFly의 주요 행동 색은 #0066CC다. 브랜드 규정의 빨강과 제품 토큰의 brand 역할 이름을 같은 뜻으로 취급하지 않는다. 이 문서의 --color-primary-*는 룩북의 브랜드 팔레트이고, 버튼과 포커스에는 --action-*를 연결한다. 로고와 위험 알림에 빨강이 동시에 나타나더라도 행동 버튼까지 모두 빨갛게 만들지 않는다.
색 원본은 tokens-palette.scss, 역할 매핑은 tokens-default.scss와 tokens-dark.scss, 차트는 tokens-charts.scss와 tokens-charts-dark.scss에서 확인했다. 아래 별칭 이름은 룩북 안에서 쓰기 쉽게 붙인 이름이다. 별칭 자체를 PatternFly의 공식 토큰 이름이라고 부르지 않는다. 원본의 이름과 값을 함께 적어 구현자가 추적할 수 있게 한다.
| 역할 | 공식 원본 또는 구성 | 라이트 | 다크 | |---|---|---|---| | 주요 행동 | --pf-t--global--color--brand--default | #0066CC | #92C5F9 | | 행동 hover·clicked | --pf-t--global--color--brand--hover / --clicked | #004D99 | #B9DAFC | | 링크 | --pf-t--global--text--color--link--default | #0066CC | #B9DAFC | | 본문 | --pf-t--global--text--color--regular | #151515 | #FFFFFF | | 보조 글자 | --pf-t--global--text--color--subtle | #4D4D4D | #C7C7C7 | | 비활성 글자 | --pf-t--global--text--color--disabled | #A3A3A3 | #707070 | | 주 표면 | --pf-t--global--background--color--primary--default | #FFFFFF | #292929 | | 보조 표면 | --pf-t--global--background--color--secondary--default | #F2F2F2 | #151515 | | 떠 있는 표면 | --pf-t--global--background--color--floating--default | #FFFFFF | #383838 | | 기본 선 | --pf-t--global--border--color--default | #C7C7C7 | #A3A3A3 | | 성공 글자 | --pf-t--global--text--color--status--success--default | #3D7317 | #87BB62 | | 정보 글자 | --pf-t--global--text--color--status--info--default | #5E40BE | #B6A6E9 |
빨강 스케일의 100·200·300·400·500·600·700·900은 공식 red 10·20·30·40·50·60·70·80 값을 옮겼다. 룩북 형식을 채우기 위한 50의 #FFF4F4와 800의 #4F0000은 보간값(추정)이다. 예시의 주요 상태색에는 쓰지 않는다. 중성색도 공식 gray 계열에서 가져오되 50·100 같은 별칭 단계는 이 문서의 매핑이다.
상태 배경은 공식 팔레트의 밝은 10 단계와 어두운 80 단계를 조합했다. 성공은 green, 경고는 yellow, 오류는 red-orange, 정보는 purple이다. 배경 역할에 이 색을 선택한 매핑은 추정이고 색값 자체는 공식이다. 경고 배지의 글자는 본문색을 써 밝은 노랑 바탕과 구분한다. 작은 배지는 Warning과 Failed라는 단어 또는 모양을 함께 표시한다. 상태를 색만으로 전달하지 않는다.
Topology의 Running은 파랑이다. 공식 노드 참고 화면의 파드 링을 따른다. 일반 상태 카드의 Healthy 초록과 같은 범례로 합치지 않는다. Pending은 회색, Failed는 경고 계열의 진한 주황빨강을 사용한다. 원의 일부만 대기일 때 링 전체를 대기색으로 바꾸지 않고 해당 비율을 구간으로 나눈다. 예시의 jobs는 파드 두 개 가운데 하나가 대기 중인 상태다.
차트의 blue 300은 라이트 #0066CC, 다크 #92C5F9다. 다크를 단순히 검은 바탕으로 바꾸면 파란 선이 잘 보이지 않는다. 공식 차트 토큰은 어두운 표면에서 밝은 계열을 사용한다. CPU와 Network에는 blue, Memory에는 purple, Filesystem에는 teal을 사용했다. 각 자원의 색 선택은 문서의 구분 방식(추정)이며 축·단위·제목을 지워도 되는 근거가 되지 않는다.
:root {
--color-primary-50:#fff4f4; /* 보간값(추정) */
--color-primary-100:#fce3e3;
--color-primary-200:#fbc5c5;
--color-primary-300:#f9a8a8;
--color-primary-400:#f56e6e;
--color-primary-500:#ee0000;
--color-primary-600:#a60000;
--color-primary-700:#5f0000;
--color-primary-800:#4f0000; /* 보간값(추정) */
--color-primary-900:#3f0000;
--color-neutral-0:#ffffff;
--color-neutral-50:#f2f2f2;
--color-neutral-100:#e0e0e0;
--color-neutral-200:#c7c7c7;
--color-neutral-300:#a3a3a3;
--color-neutral-400:#707070;
--color-neutral-500:#4d4d4d;
--color-neutral-600:#383838;
--color-neutral-700:#292929;
--color-neutral-800:#1f1f1f;
--color-neutral-900:#151515;
--color-neutral-1000:#000000;
--font-display:'Red Hat Display','RedHatDisplay','Noto Sans KR','Segoe UI',Arial,sans-serif;
--font-body:'Red Hat Text','RedHatText','Noto Sans KR','Segoe UI',Arial,sans-serif;
--font-mono:'Red Hat Mono','RedHatMono','Courier New',monospace;
--font-size-display:36px;
--font-size-h1:24px;
--font-size-h2:20px;
--font-size-h3:18px;
--font-size-body-large:16px;
--font-size-body:14px;
--font-size-small:12px;
--font-weight-regular:400;
--font-weight-strong:500;
--font-weight-bold:700;
--line-height-body:1.5;
--line-height-heading:1.3;
--action-default:#0066cc;
--action-hover:#004d99;
--action-active:#004d99;
--action-text:#ffffff;
--link-default:#0066cc;
--link-hover:#004d99;
--bg-base:#f2f2f2;
--bg-subtle:#f2f2f2;
--bg-elevated:#ffffff;
--bg-overlay:#ffffff;
--text-primary:#151515;
--text-secondary:#4d4d4d;
--text-tertiary:#4d4d4d;
--text-disabled:#a3a3a3;
--text-on-primary:#ffffff;
--border-default:#c7c7c7;
--border-focus:#0066cc;
--color-success-fg:#3d7317;
--color-warning-fg:#dca614;
--color-error-fg:#b1380b;
--color-info-fg:#5e40be;
--danger-action:#b1380b;
--danger-hover:#731f00;
--danger-active:#731f00;
--danger-text:#ffffff;
--color-success-bg:#e9f7df;
--color-warning-bg:#fff4cc;
--color-error-bg:#ffe3d9;
--color-info-bg:#ece6ff;
--border-subtle:#e0e0e0;
--border-strong:#4d4d4d;
--control-bg:#ffffff;
--disabled-bg:#e0e0e0;
--selection-bg:#e0f0ff;
--chart-blue:#0066cc;
--chart-purple:#5e40be;
--chart-teal:#37a3a3;
}
:root[data-theme="light"] {
--action-default:#0066cc;
--action-hover:#004d99;
--action-active:#004d99;
--action-text:#ffffff;
--link-default:#0066cc;
--link-hover:#004d99;
--bg-base:#f2f2f2;
--bg-subtle:#f2f2f2;
--bg-elevated:#ffffff;
--bg-overlay:#ffffff;
--text-primary:#151515;
--text-secondary:#4d4d4d;
--text-tertiary:#4d4d4d;
--text-disabled:#a3a3a3;
--text-on-primary:#ffffff;
--border-default:#c7c7c7;
--border-focus:#0066cc;
--color-success-fg:#3d7317;
--color-warning-fg:#dca614;
--color-error-fg:#b1380b;
--color-info-fg:#5e40be;
--danger-action:#b1380b;
--danger-hover:#731f00;
--danger-active:#731f00;
--danger-text:#ffffff;
--color-success-bg:#e9f7df;
--color-warning-bg:#fff4cc;
--color-error-bg:#ffe3d9;
--color-info-bg:#ece6ff;
--border-subtle:#e0e0e0;
--border-strong:#4d4d4d;
--control-bg:#ffffff;
--disabled-bg:#e0e0e0;
--selection-bg:#e0f0ff;
--chart-blue:#0066cc;
--chart-purple:#5e40be;
--chart-teal:#37a3a3;
}
:root[data-theme="dark"] {
--action-default:#92c5f9;
--action-hover:#b9dafc;
--action-active:#b9dafc;
--action-text:#1f1f1f;
--link-default:#b9dafc;
--link-hover:#e0f0ff;
--bg-base:#151515;
--bg-subtle:#151515;
--bg-elevated:#292929;
--bg-overlay:#383838;
--text-primary:#ffffff;
--text-secondary:#c7c7c7;
--text-tertiary:#c7c7c7;
--text-disabled:#707070;
--text-on-primary:#1f1f1f;
--border-default:#a3a3a3;
--border-focus:#92c5f9;
--color-success-fg:#87bb62;
--color-warning-fg:#ffcc17;
--color-error-fg:#f89b78;
--color-info-fg:#b6a6e9;
--danger-action:#f0561d;
--danger-hover:#f4784a;
--danger-active:#f4784a;
--danger-text:#1f1f1f;
--color-success-bg:#183301;
--color-warning-bg:#54330b;
--color-error-bg:#4c1405;
--color-info-bg:#1b0d33;
--border-subtle:#4d4d4d;
--border-strong:#c7c7c7;
--control-bg:#383838;
--disabled-bg:#383838;
--selection-bg:#003366;
--chart-blue:#92c5f9;
--chart-purple:#876fd4;
--chart-teal:#9ad8d8;
}
페이지의 --bg-base는 secondary 표면, 카드의 --bg-elevated는 primary 표면에 연결했다. 메뉴와 카드 사이의 약한 구분선은 gray 20/60을 사용한 매핑(추정)이다. 이는 기본 선 토큰을 바꾼 것이 아니라 별도 --border-subtle 역할이다. 다크에서 모든 선을 #A3A3A3으로 그리면 표가 과하게 쪼개져 보여서, 입력과 강한 경계에만 기본 선을 쓰고 목록 구분에는 약한 선을 둔다.
Typography
타이포그래피
Red Hat Display는 제목, Red Hat Text는 본문, Red Hat Mono는 식별 문자열에 쓴다. Red Hat 공식 서체 규정과 PatternFly font-family 토큰이 같은 세 계열을 사용한다. Display의 개방된 형태와 중간 굵기는 작은 제목에서도 위계를 만들고, Text는 긴 표와 설정 설명을 안정적으로 묶는다. 관리 화면에서 모든 글자를 Mono로 바꾸지 않는다. 일정한 폭이 필요한 문자열과 일반 문장의 읽기 방식이 다르기 때문이다.
- 영문 페어링: Red Hat Display / Red Hat Text / Red Hat Mono. 공식 서체 규정에서 용도별로 안내하는 세 계열이다.
- 한글 페어링: 공식 토큰의 대체 서체 목록에 있는 Noto Sans KR을 사용한다. Red Hat 서체 자체가 한글 글리프를 모두 제공한다는 뜻은 아니다. 해당 서체가 없으면 시스템 sans-serif로 대체한다.
- 룩북 렌더: 외부 글꼴 파일을 불러오지 않는다. CSS에 브랜드 서체 이름을 먼저 지정하고 Segoe UI·Arial·시스템 대체 서체를 뒤에 둔다. 실제 렌더의 자폭은 설치된 대체 서체에 따라 조금 달라질 수 있다.
| 위계 | 크기 | 굵기 | 줄 높이 | 자간 | 적용 | |---|---:|---:|---:|---|---| | Display | 36px | 500 | 1.3 | normal | 공식 크기 primitive 800을 사용한 큰 요약 제목 조합(추정) | | H1 | 24px | 500 | 1.3 | normal | Overview 같은 페이지 이름 | | H2 | 20px | 500 | 1.3 | normal | 주요 구획 제목 | | H3 | 18px | 500 | 1.3 | normal | 구획 안의 큰 카드 묶음 | | Body Large | 16px | 400 | 1.5 | normal | 설명이 길지 않은 도입 문장 | | Body | 14px | 400 | 1.5 | normal | 일반 본문과 폼 | | Body Small | 12px | 400 | 1.5 | normal | 보조 설명과 표의 작은 문장 | | Caption | 12px | 400 | 1.5 | normal | 시각·범례·단위, 공식 body small 재사용 |
표의 H1·H2·H3는 공식 heading 토큰이다. H4·H5·H6은 모두 16px이므로 숫자 단계마다 기계적으로 크기를 줄이지 않는다. 일반 본문 강조는 공식 strong 500, 더 강한 제목 강조는 700을 사용한다. 600을 중간 단계로 임의 추가하는 대신 역할이 정말 다른지 먼저 확인한다. 제목이 짧아도 대문자와 큰 자간을 붙여 새로운 스타일을 만들지 않는다.
⑫의 16px 카드 제목, 11–12px 목록, 10px 단위·시각은 900px 프레임용 축약값(추정)이다. 실제 앱은 기본 body 14px와 충분한 조작 영역을 기준으로 구현한다.
숫자는 font-variant-numeric: tabular-nums로 정렬한다. 31.4%, 62.8 GiB, 38.6 MB/s에서 소수점과 단위를 분리해 쓰되 줄바꿈으로 의미가 갈라지지 않게 한다. 주소는 overflow-wrap:anywhere를 허용하고, 자원 이름은 Mono로 보여도 링크 색을 유지한다. 0과 O, 1과 l이 섞이는 식별 문자열에 장식용 Display를 사용하지 않는다.
Spacing
스페이싱
간격은 4px 단위를 바탕으로 4·8·16·24·32·48·64·80px를 사용한다. PatternFly의 공식 spacer xs·sm·md·lg·xl·2xl·3xl·4xl 값이다. 8px 다음이 16px라는 점을 유지한다. 예를 들어 표의 모든 세로 간격을 12px로 맞추고 이를 공식 md라고 부르면 토큰의 의미가 달라진다. 12px가 필요한 축약 화면에는 별도 조합값이라고 적는다.
| 토큰 | 값 | 관리 콘솔에서의 용도 | |---|---:|---| | xs | 4px | 아이콘과 짧은 상태 단어, 작은 레이블과 수치의 내부 간격 | | sm | 8px | 버튼 안의 아이콘과 문구, 폼 보조 문장, 관련 항목끼리 묶기 | | md | 16px | 작은 카드 내부와 촘촘한 카드 사이, 기본 페이지 안쪽 간격 | | lg | 24px | 일반 패널 내부, 서로 다른 그룹 사이의 한 단계 큰 간격 | | xl | 32px | 큰 화면의 구획 경계, 넓은 페이지 상하 여백 | | 2xl | 48px | 문서나 초기 안내 화면의 큰 덩어리 사이 | | 3xl·4xl | 64·80px | 여유 있는 소개 화면. 상태 표의 행 높이에 그대로 쓰지 않음 |
콘솔 컨테이너는 가용 폭을 쓴다. 모든 페이지를 좁은 960px 중앙 박스 안에 가두면 오른쪽 서랍과 그래프가 공간을 다투게 된다. 왼쪽 탐색의 폭을 먼저 확보하고 남은 영역에 minmax(0,1fr)를 쓴다. 이 예시의 왼쪽 탐색 132px, 본문 바깥 간격 16px, 세 열 간격 12px는 900px 프레임에 맞춘 축약값(추정)이다.
노드 안쪽은 내용의 크기보다 관계의 구분을 우선한다. 링과 런타임 그림 사이, 링과 주변 액션 배지 사이에 간격을 둔다. 라벨은 노드 아래 중앙에 붙여 어느 노드의 이름인지 즉시 알 수 있게 한다. 연결선이 라벨을 가로지르면 노드 위치나 경로를 움직이며, 글자 뒤에 큰 흰 박스를 덧대어 선을 무작정 가리는 방식은 피한다.
:root {
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 32px;
--space-2xl: 48px;
--space-3xl: 64px;
--space-4xl: 80px;
--container-max: none;
--container-padding: var(--space-lg);
}
.rh-container { width:100%; max-width:var(--container-max); padding-inline:var(--container-padding); }
@media (max-width: 540px) { .rh-container { --container-padding:var(--space-md); } }
모바일에서는 작은 세 열을 그대로 축소하지 않는다. 상태와 사용량을 먼저 읽고 Details·Inventory, Activity·Alerts, 자원 표로 내려가도록 순서를 바꾼다. 보조 열을 없애는 대신 세로 흐름에 합친다. 표는 핵심 이름·상태·가동 시간을 남기고 일부 세부 열을 접은 축약 예시다. 실제 제품에서는 행을 열어 상세 정보를 볼 수 있는 동작을 함께 제공한다.
Border Radius
Border Radius
PF6는 카드 16px, 폼 6px, 버튼 999px를 구분한다. 공식 radius 토큰은 sharp 0, tiny 4, small 6, medium 16, large 24, pill 999다. 특히 PF5처럼 네모난 버튼을 기본으로 그리면 PF6의 인상이 약해진다. 카드와 버튼을 모두 6px로 통일하는 것도 같은 문제다. 아래의 sm·md·lg 이름은 룩북 별칭이며 실제 원본 크기와 대응을 적었다.
| 원본 | 룩북 별칭 | 값 | 적용 | |---|---|---:|---| | sharp | none | 0 | 표, 머리띠, 화면 경계를 붙여 놓는 영역 | | tiny | xs | 4px | 아주 작은 보조 표시와 내부 조각 | | small | sm | 6px | 입력, 검색, 선택 컨트롤 | | medium | md | 16px | PF6 카드와 일반 패널 | | large | lg·xl | 24px | 큰 둥근 컨테이너가 필요한 별도 조합 | | pill | full | 999px | 버튼과 캡슐형 배지 |
:root {
--radius-none:0;
--radius-xs:4px;
--radius-sm:6px;
--radius-md:16px;
--radius-lg:24px;
--radius-xl:24px;
--radius-full:999px;
}
Topology의 원형 노드와 애플리케이션 묶음은 별도 시각 문법이다. 노드는 리소스와 파드 상태를 읽는 원이고, 묶음의 큰 곡선은 여러 노드가 같은 애플리케이션에 속함을 알린다. 이것을 전역 radius 24px 카드와 같은 규칙으로 처리하지 않는다. 묶음 테두리는 그래프 크기에 맞춘 SVG 곡선(추정)이고, 서랍과 머리띠는 화면 가장자리에 붙는다.
Nested 카드가 필요할 때 모든 안쪽 줄에도 16px 테두리를 반복하지 않는다. Details의 속성 목록은 간격으로 나누고, Activity는 작은 아이콘과 시간으로 구분한다. 둥근 외곽은 구획 경계에만 남겨 밀도가 높은 콘솔이 둥근 상자의 모음으로 보이지 않게 한다. 상태 배지는 둥글어도 표의 수치 정렬은 반듯하게 유지한다.
Shadow / Elevation
Shadow / Elevation
기본 면은 얇은 경계와 약한 그림자로 구분한다. 공식 shadow 토큰은 방향·거리·흐림·색이 나뉘어 있다. 아래 CSS는 그 원자 값을 CSS의 한 줄 그림자로 합친 것이다. sm-default는 x 0, y 1, blur 4, spread 0이며 md-default는 0·4·9·0, lg-default는 0·10·20·0이다. 공식 원본에 없는 단일 합성 토큰 이름을 실제 이름인 것처럼 쓰지 않는다.
라이트의 default 그림자 색은 rgba(41,41,41,.15), 다크의 default는 rgba(0,0,0,.5)다. 방향별 보조 그림자에는 더 짙은 다크 값이 있지만 여기서는 default 조합을 사용한다. 표면 색과 기본 선이 먼저 경계를 만들고 그림자는 떠 있는 정도를 보완한다. 다크에서 그림자만 키워서는 검정과 진회색의 경계가 해결되지 않는다.
:root {
--shadow-none:none;
--shadow-sm:0 1px 4px 0 rgba(41,41,41,.15);
--shadow-md:0 4px 9px 0 rgba(41,41,41,.15);
--shadow-lg:0 10px 20px 0 rgba(41,41,41,.15);
--shadow-xl:var(--shadow-lg);
}
:root[data-theme="light"] {
--shadow-none:none;
--shadow-sm:0 1px 4px 0 rgba(41,41,41,.15);
--shadow-md:0 4px 9px 0 rgba(41,41,41,.15);
--shadow-lg:0 10px 20px 0 rgba(41,41,41,.15);
--shadow-xl:var(--shadow-lg);
}
:root[data-theme="dark"] {
--shadow-none:none;
--shadow-sm:0 1px 4px 0 rgba(0,0,0,.5);
--shadow-md:0 4px 9px 0 rgba(0,0,0,.5);
--shadow-lg:0 10px 20px 0 rgba(0,0,0,.5);
--shadow-xl:var(--shadow-lg);
}
| 단계 | 적용 | 주의할 점 | |---|---|---| | none | 그래프 캔버스, 표, 탭 | 선과 여백으로 정보 그룹을 읽게 함 | | sm | 기본 카드 | 카드끼리 높낮이 경쟁을 만들지 않음 | | md | 선택한 떠 있는 패널, 팝오버 | 열린 상태에 사용하고 모든 카드 hover에 강요하지 않음 | | lg | 큰 대화상자, 오버레이 | 배경 차단과 포커스 처리가 별도로 필요 | | xl | lg와 같은 값 재사용 | 확인하지 않은 큰 그림자 수치를 새 공식 단계로 만들지 않음 |
카드 hover에 그림자를 넣더라도 내용이 위로 떠서 표의 기준선이 달라지지 않게 한다. 관리 화면의 주요 이동은 목록·상세 서랍·탭 전환이므로 제품 사진 카드처럼 translateY를 반복할 이유가 없다. Topology 런타임 그림 안의 하이라이트와 그림자는 삽화의 입체감이고 UI 패널의 elevation과는 다르다. 테마를 바꿔도 그림의 재질은 유지하고 노드 표면과 글자만 역할에 맞춰 바꾼다.
Iconography
Iconography
작업 아이콘과 상태 아이콘을 구분해 섞는다. PatternFly는 Font Awesome 기반 아이콘과 자체 아이콘을 함께 제공한다. 검색·필터·더 보기·확대·축소 같은 동작은 작은 단색 형태로, 정상·경고·실패는 체크·삼각형·오류 기호로 읽게 한다. 추천 구현은 해당 버전의 @patternfly/react-icons다. 다른 라이브러리를 쓰는 경우 외곽선 하나만 닮았다고 같은 의미라고 판단하지 않는다.
- 스타일: Filled 중심의 공식 제품 아이콘과 필요한 Outline의 혼합. 상태에는 모양과 색을 함께 사용한다.
- 선 굵기: 공식 아이콘 전부에 공통 stroke px가 있는 것은 아니다. 이 자급형 SVG 예시는 24 단위 격자에서 1.7px 선을 사용한 재구성(추정)이다.
- 모서리: 작은 동작 아이콘은 round cap·round join으로 축소 시 뭉침을 줄였다. 리소스 종류는 D·SS 같은 글자 배지로 따로 보여 준다.
- 크기: 실제 UI에서는 16px 기본 아이콘을 쓰고 텍스트와 수직 중심을 맞춘다. 카드 안 7–9px 아이콘은 280px 미리보기에 맞춘 축약값(추정)이다.
노드의 오른쪽 위에는 외부 경로 열기, 왼쪽 아래에는 빌드 결과, 오른쪽 아래에는 편집 동작을 둔다. 같은 원형 배지여도 안쪽 모양이 달라야 한다. 실제 공식 스크린샷에서는 Git 출처나 빌드 상태 등 자원에 따라 배지 구성이 달라진다. 예시는 이 위치 문법을 따르되 모든 배포에 똑같은 기능이 있다고 단정하지 않는다.
다섯 런타임 삽화는 각각 다른 외형과 재질을 가진다. 웹 UI는 붉은 브라우저와 받침대, API는 푸른 입방체, jobs는 보라 기어와 재생 표식, cache는 황금색 겹판과 번개, db는 청록 원통과 상태등이다. 바탕 형태·면 그라디언트·어두운 옆면·밝은 반사·세부 부품을 따로 그렸다. 이 그림은 공식 런타임 아이콘 목록의 일부가 아니라 종류를 구분하기 위한 예시다.
아이콘을 단독 버튼으로 쓸 때 접근 가능한 이름을 준다. 장식 아이콘은 aria-hidden으로 중복 읽기를 막고 실제 문구는 HTML로 남긴다. 그래프에는 전체 관계를 요약하는 이름과 각 노드의 상태 설명을 붙인다. 단순한 정적 그림에 가짜 버튼 역할과 포커스를 잔뜩 부여하면 키보드 탐색만 길어지므로 동작하는 컴포넌트와 삽화를 구별한다.
Component Guide
컴포넌트 가이드
버튼은 PF6의 pill 모양과 파란 action 역할을 사용한다. Primary는 저장·생성과 같은 주요 행동, Secondary는 설정 보기나 보조 진행, Ghost는 문맥 안의 가벼운 동작, Danger는 되돌리기 어려운 삭제에 쓴다. Danger가 브랜드 빨강과 비슷해 보여도 로고의 색을 가져다 이름만 바꾸지 않는다. 상태 토큰의 위험 역할을 연결해 다크에서 대비가 유지되게 한다.
| 종류 | Default | Hover | Active | Disabled | Loading | |---|---|---|---|---|---| | Primary | 파란 면·대비 글자 | brand hover | brand clicked | 비활성 표면·글자 | 폭 유지, 작은 회전 링 | | Secondary | 파란 선·투명 면 | 옅은 선택 면 | 더 뚜렷한 선 | 회색 선·글자 | 선 유지, 문구 옆 진행 표시 | | Ghost | 링크색 글자 | 보조 표면 | 선택 표면 | 비활성 글자 | 위치를 바꾸지 않고 상태 알림 | | Danger | 위험색 면 | 한 단계 강한 위험색 | 눌림 위험색 | 공통 비활성 처리 | 중복 실행 차단과 진행 문구 |
원본 Button SCSS의 색과 모서리를 적용한 축약 예시다. 다크 Primary에는 밝은 파란 면과 진한 글자를 조합한다. Disabled는 글자·배경·선을 각각 매핑한다.
Input은 6px 모서리와 오류 문구가 함께 움직인다. Focus는 파란 외곽선으로 보이고 Error는 위험색 경계와 아래 설명을 함께 사용한다. 입력값이 잘못된 이유를 작은 빨간 선만으로 알리지 않는다. Disabled 필드는 일반 본문과 구별하되 내용을 읽을 수 있게 남긴다. 읽기 전용과 비활성은 목적이 다르므로 실제 폼에서는 키보드 접근과 복사 가능 여부를 별도로 정한다.
Card는 16px 외곽 안에서 제목과 본문을 정렬한다. 기본형은 작은 그림자, 강조형은 명시적인 선택 경계, Outlined는 그림자 없이 선으로 구분한다. 강조형을 경고 카드로 겸용하지 않는다. Alert는 제목·심각도·설명·행동이 있는 별도 패턴이다. Status의 Healthy와 Activity의 업데이트 성공은 작은 상태 표식만으로도 충분하며, 카드 전체를 녹색으로 채울 필요가 없다.
Badge는 짧은 범주 또는 상태에만 사용한다. Solid는 강한 구분, Subtle은 부드러운 배경, Outline은 주변을 덜 가리는 범주 표시에 적합하다. 긴 문장이나 여러 줄 설명을 배지 안에 넣지 않는다. 버전은 일반 수치, Live는 짧은 상태, Warning은 의미가 있는 상태다. 같은 크기의 캡슐이어도 링크·삭제 버튼·단순 레이블을 혼동하지 않도록 실제 상호작용 요소를 구분한다.
Navigation은 현재 위치를 텍스트·배경·선으로 표시한다. Topology 카드에는 Developer 메뉴, Overview 예시에는 Administrator의 Home 하위 메뉴를 보여 준다. 4.19 통합 기본 메뉴와 과거 perspective 메뉴가 완전히 같다고 설명하지 않는다. 화면 전환에 따라 프로젝트·클러스터 범위가 달라지므로 탐색의 선택 상태와 범위 선택을 한 가지 UI로 합치지 않는다.
.rh-btn {
display:inline-flex; align-items:center; justify-content:center; gap:var(--space-sm);
min-height:36px; padding:6px 16px; border:1px solid transparent;
border-radius:var(--radius-full); font:500 var(--font-size-body)/1.5 var(--font-body);
white-space:nowrap; background:var(--button-bg); color:var(--button-fg);
border-color:var(--button-border,transparent);
}
.rh-btn-primary { --button-bg:var(--action-default); --button-fg:var(--action-text); }
.rh-btn-primary:hover { --button-bg:var(--action-hover); }
.rh-btn-primary:active { --button-bg:var(--action-active); }
.rh-btn-secondary { --button-bg:transparent; --button-fg:var(--action-default); --button-border:var(--action-default); }
.rh-btn-secondary:hover { --button-bg:var(--selection-bg); --button-fg:var(--action-hover); --button-border:var(--action-hover); }
.rh-btn-secondary:active { --button-bg:var(--selection-bg); --button-fg:var(--action-active); --button-border:var(--action-active); }
.rh-btn-ghost { --button-bg:transparent; --button-fg:var(--link-default); }
.rh-btn-ghost:hover { --button-bg:var(--bg-subtle); --button-fg:var(--link-hover); }
.rh-btn-ghost:active { --button-bg:var(--selection-bg); --button-fg:var(--action-active); }
.rh-btn-danger { --button-bg:var(--danger-action); --button-fg:var(--danger-text); }
.rh-btn-danger:hover { --button-bg:var(--danger-hover); }
.rh-btn-danger:active { --button-bg:var(--danger-active); }
.rh-btn:focus-visible { outline:2px solid var(--border-focus); outline-offset:3px; }
.rh-btn:disabled,.rh-btn[aria-disabled="true"] {
--button-bg:var(--disabled-bg); --button-fg:var(--text-disabled); --button-border:transparent;
cursor:not-allowed; box-shadow:none;
}
.rh-btn-secondary:disabled,.rh-btn-secondary[aria-disabled="true"] { --button-bg:transparent; --button-border:var(--text-disabled); }
.rh-btn-ghost:disabled,.rh-btn-ghost[aria-disabled="true"] { --button-bg:transparent; }
.rh-btn[aria-busy="true"] { cursor:progress; }
.rh-btn[aria-busy="true"]::before { content:""; width:12px; height:12px; box-sizing:border-box; border:2px solid currentColor; border-right-color:transparent; border-radius:50%; }
.rh-input {
width:100%; min-height:36px; padding:6px 8px; border:1px solid var(--border-default);
border-bottom-color:var(--border-strong); border-radius:var(--radius-sm);
background:var(--control-bg); color:var(--text-primary); font:400 14px/1.5 var(--font-body);
}
.rh-input:hover { border-color:var(--border-strong); }
.rh-input:focus { outline:2px solid var(--border-focus); outline-offset:1px; border-color:var(--border-focus); }
.rh-input[aria-invalid="true"] { border-color:var(--color-error-fg); border-bottom-width:2px; }
.rh-field-error { color:var(--color-error-fg); font-size:12px; margin-top:var(--space-xs); }
.rh-input:disabled { background:var(--disabled-bg); color:var(--text-disabled); border-color:var(--border-subtle); cursor:not-allowed; }
.rh-panel { background:var(--bg-elevated); border:1px solid transparent; border-radius:var(--radius-md); box-shadow:var(--shadow-sm); }
.rh-panel-emphasis { border-color:var(--action-default); box-shadow:var(--shadow-sm); }
.rh-panel-outlined { border-color:var(--border-default); box-shadow:var(--shadow-none); }
.rh-badge { display:inline-flex; align-items:center; gap:4px; padding:1px 7px; border:1px solid transparent; border-radius:var(--radius-full); font:400 12px/1.5 var(--font-body); white-space:nowrap; }
.rh-badge-solid { background:var(--action-default); color:var(--action-text); }
.rh-badge-subtle { background:var(--color-success-bg); color:var(--color-success-fg); }
.rh-badge-outline { background:transparent; color:var(--text-secondary); border-color:var(--border-default); }
.rh-badge-warning { background:var(--color-warning-bg); color:var(--text-primary); }
.rh-navigation { background:var(--bg-elevated); border-right:1px solid var(--border-subtle); }
.rh-navigation a { display:block; padding:12px 16px; color:var(--text-primary); text-decoration:none; }
.rh-navigation a:hover { background:var(--bg-subtle); }
.rh-navigation a[aria-current="page"] { color:var(--link-default); background:var(--bg-subtle); box-shadow:inset 3px 0 var(--action-default); font-weight:500; }
.rh-navigation a:focus-visible { outline:2px solid var(--border-focus); outline-offset:-2px; }
노드 상세 서랍의 Details·Resources·Observe는 같은 자원의 관점을 바꾼다. Details에는 이름·이미지·포트, Resources에는 연결된 Pods·Builds·Services·Routes, Observe에는 사용량을 배치하는 원칙이다. 이 예시는 Details만 열린 정적 장면이다. 실제 구현에서 탭을 누르면 내용·선택 속성·키보드 이동이 함께 바뀌어야 하며, 서랍을 닫았을 때 원래 선택한 노드로 포커스가 돌아가야 한다.
로딩 중에는 자원 수를 0으로 덮어쓰지 않는다. 마지막 값을 보여 주면서 갱신 중임을 알리거나 처음 로딩에만 스켈레톤을 쓴다. Empty는 프로젝트에 자원이 없는 상태, Error는 가져오지 못한 상태이므로 문구와 다음 행동이 다르다. Topology에서 노드가 없을 때는 프로젝트 선택과 Add 경로가 유용하고, 권한이 없을 때는 권한 범위를 설명하는 문구가 필요하다. 정적 룩북 예시에는 성공적으로 로드된 한 장면만 담았다.
Motion
Motion
움직임은 변화한 위치와 진행 상태를 알리는 데 쓴다. PF6 duration primitive는 50·100·200·300·400·500·600ms다. 기본 easing은 cubic-bezier(.4,0,.2,1), accelerate는 (.4,0,.7,.2), decelerate는 (0,0,.2,1)로 확인했다. 아래 fast 100, base 200, slow 300 별칭은 공식 primitive 중 하나를 골라 연결한 문서 매핑(추정)이다.
| 동작 | 적용 시간 | 곡선 | 이유 | |---|---:|---|---| | 버튼·탭의 색 변화 | 100ms | default | 클릭 반응을 빠르게 알림 | | 상세 서랍 등장 | 200ms | decelerate | 선택한 노드와 내용의 관계를 유지 | | 그래프 배치 변경 | 300ms | default | 노드가 어디로 이동했는지 추적 | | 경로 전환 뒤 강조 | 200ms | default | 새로운 선택 위치만 잠깐 드러냄 | | 지속적인 진행 링 | 600ms 반복 | linear | 완료 시각을 약속하지 않는 진행 표시. 조합 방식은 추정 |
:root {
--duration-instant:50ms;
--duration-fast:100ms;
--duration-base:200ms;
--duration-slow:300ms;
--duration-extended:400ms;
--duration-long:500ms;
--duration-loop:600ms;
--ease-out:cubic-bezier(0,0,.2,1);
--ease-in:cubic-bezier(.4,0,.7,.2);
--ease-in-out:cubic-bezier(.4,0,.2,1);
}
.rh-btn { transition:background-color var(--duration-fast) var(--ease-in-out), color var(--duration-fast) var(--ease-in-out), border-color var(--duration-fast) var(--ease-in-out); }
.rh-drawer-enter { animation:rh-drawer-in var(--duration-base) var(--ease-out); }
.rh-btn[aria-busy="true"]::before { animation:rh-spin var(--duration-loop) linear infinite; }
@keyframes rh-drawer-in { from { opacity:0; transform:translateX(16px); } to { opacity:1; transform:translateX(0); } }
@keyframes rh-spin { to { transform:rotate(360deg); } }
@media (prefers-reduced-motion:reduce) {
.rh-btn,.rh-drawer-enter { transition:none; animation:none; }
.rh-btn[aria-busy="true"]::before { animation:none; border-style:dotted; }
}
정상 상태의 파드 링을 계속 회전시키지 않는다. 회전은 작업 중이라는 뜻으로 오해될 수 있다. 데이터 갱신 때도 모든 그래프를 처음부터 다시 그리면 추세를 비교하기 어렵다. 축과 범례를 고정하고 바뀐 값만 조용히 갱신한다. 알림 배지를 계속 깜빡이게 하는 대신 새 알림 수와 읽지 않은 상태를 명시한다. 작은 룩북 카드와 ⑫는 눈으로 수치를 비교할 수 있도록 정지 상태로 그렸다.
동작 줄이기 환경에서는 위치 이동과 회전이 없어도 상태를 알 수 있어야 한다. 진행 버튼의 문구를 Loading처럼 바꾸고 aria-busy를 함께 설정한다. 실제 비동기 작업은 완료·실패를 별도 상태 문구로 알려야 하며, 애니메이션이 멈춘 사실을 완료로 해석하지 않는다. 긴 실행은 취소 가능 여부와 마지막 갱신 시각을 보여 주는 편이 작은 효과를 더하는 것보다 유용하다.
Anti-patterns
Anti-patterns
1. 브랜드 빨강으로 링크·실행·선택을 모두 칠하지 않는다. Red Hat의 #EE0000은 식별을 담당하고 PatternFly action blue는 조작을 담당한다. 삭제와 오류는 상태 역할을 따른다. 검정 머리띠에 붉은 로고만 있어도 브랜드가 읽히며, 파란 선택선은 어느 자원을 보고 있는지 알려 준다. 빨간 노드 다섯 개를 나란히 놓으면 정상과 실패를 구분할 수 없다. 2. Topology를 숫자 카드와 추세선 대시보드로 바꾸지 않는다. 이 장면의 주인공은 배포 노드, 파드 링, 연결선, 애플리케이션 묶음, 상세 서랍이다. CPU 그래프는 Overview나 Observe에 둔다. 카드의 좁은 공간에도 다섯 가지 런타임 외형과 자원 종류, 현재 상태를 남겨 관계를 읽게 한다. 그래프가 단순한 배경 무늬가 되면 OpenShift 장면을 선택한 의미가 사라진다. 3. PF6 컴포넌트를 모두 네모나게 만들거나 모두 같은 반지름으로 만들지 않는다. 입력 6px, 카드 16px, 버튼 pill의 차이를 유지한다. 반대로 머리띠와 표 전체를 거대한 캡슐로 감싸지도 않는다. 화면 구조의 평평한 경계와 작업 컴포넌트의 둥근 경계가 공존해야 많은 정보가 안정적으로 보인다. 오래된 스크린샷의 외곽을 그대로 공식 PF6 값으로 옮기지 않는다. 4. 다크를 색 반전으로 만들지 않는다. #FFFFFF를 검정으로 바꾸는 것만으로는 링크·성공·오류·차트의 대비가 맞지 않는다. primary #292929, secondary #151515, floating #383838의 역할을 유지한다. 밝은 파란 버튼은 진한 글자와 짝지으며, 정보 상태는 PF6의 보라 역할을 쓴다. 정상 파드 링과 성공 알림의 색이 다른 이유도 그대로 남긴다. 5. 상태를 단위 없는 숫자와 색만으로 전달하지 않는다. 62.8 옆에 GiB, 38.6 옆에 MB/s, 파드 수 옆에 Running 또는 Pending을 둔다. Healthy와 Available, Failed scheduling은 서로 다른 사실을 설명한다. 마지막 갱신 시각을 빼면 오래된 정상 수치를 현재 상태로 오해할 수 있다. 작은 글자를 무작정 늘리기보다 범위·시간·단위가 하나의 읽기 덩어리가 되도록 정렬한다.
Sources
출처
- https://medium.com/patternfly/happy-6th-birthday-patternfly-1cd1f9ae1110
- https://medium.com/patternfly/welcome-to-patternfly-6-5c372f2fd5de
- https://www.redhat.com/en/about/brand/standards
- https://www.redhat.com/en/about/brand/standards/color
- https://www.redhat.com/en/about/brand/standards/typography
- https://www.patternfly.org/foundations-and-styles/design-tokens/all-design-tokens/
- https://www.patternfly.org/foundations-and-styles/typography/
- https://www.patternfly.org/foundations-and-styles/spacers/
- https://www.patternfly.org/foundations-and-styles/motion/
- https://www.patternfly.org/design-foundations/theming/dark-theme-handbook/
- https://www.patternfly.org/extensions/topology/overview/
- https://www.patternfly.org/extensions/topology/custom-nodes/
- https://www.patternfly.org/patterns/dashboard/
- https://developers.redhat.com/blog/2019/10/16/openshift-developer-perspective
- https://developers.redhat.com/articles/2025/06/26/openshift-419-brings-unified-console-developers-and-admins
- https://developers.redhat.com/articles/2022/08/23/8-ways-openshift-411-improves-developer-experience
- https://docs.redhat.com/en/documentation/openshift_container_platform/4.18/html/web_console/adding-user-preferences
- https://docs.redhat.com/en/documentation/openshift_container_platform/4.18/html/building_applications/odc-viewing-application-composition-using-topology-view
- https://raw.githubusercontent.com/openshift/console/main/frontend/public/imgs/openshift-logo.svg
- https://raw.githubusercontent.com/patternfly/patternfly/v6.4.0/src/patternfly/base/tokens/tokens-palette.scss
- https://raw.githubusercontent.com/patternfly/patternfly/v6.4.0/src/patternfly/base/tokens/tokens-default.scss
- https://raw.githubusercontent.com/patternfly/patternfly/v6.4.0/src/patternfly/base/tokens/tokens-dark.scss
- https://raw.githubusercontent.com/patternfly/patternfly/v6.4.0/src/patternfly/base/tokens/tokens-charts.scss
- https://raw.githubusercontent.com/patternfly/patternfly/v6.4.0/src/patternfly/base/tokens/tokens-charts-dark.scss
- https://raw.githubusercontent.com/patternfly/patternfly/v6.4.0/src/patternfly/components/Button/button.scss
- https://raw.githubusercontent.com/patternfly/patternfly/v6.4.0/src/patternfly/components/FormControl/form-control.scss
- https://raw.githubusercontent.com/patternfly/patternfly/v6.4.0/src/patternfly/components/Card/card.scss
Red Hat 이름과 로고는 각 권리자의 상표입니다.