Skip to content

디자인 ​

macOS 셸과 웹 플러그인이 함께 사용하는 디자인 시스템이에요. 같은 토큰과 컴포넌트로 플러그인을 내장 UI에 맞춰요.

목차 ​

  • 갤러리
  • 원칙
  • 네 개의 레이어
  • 색
  • 타이포그래피
  • 간격, 라운딩, 테두리
  • 컴포넌트 규칙
  • 다크 모드
  • 접근성
  • 플러그인에서 사용하기
  • SwiftUI에서 사용하기
  • 이 가이드가 다루는 범위

갤러리 ​

docs/design/index.html은 실제로 배포되는 스타일시트로 모든 컴포넌트를 렌더링해요. WebPackages/Bridge/theme.css와 WebPackages/Bridge/components.css를 직접 import하므로 갤러리에서 스타일 변경을 확인할 수 있어요.

bash
script/serve-design

파일을 여는 대신 서빙해 주세요. 페이지가 스타일시트를 경로로 import해요.

components.css에 클래스를 추가할 때는 같은 변경에서 갤러리에도 예시를 추가해 주세요.

앱과 갤러리는 같은 스타일시트를 사용해요. 다르게 보인다면 플러그인의 CSS나 Swift 호스트를 확인하세요. components.css를 수정하면 양쪽의 공통 디자인이 바뀌어요.

앱은 모든 WebView에 현재 외관과 팔레트 변수를 적용해요. 디바이스 플러그인에 포함된 스타일시트가 Mac 셸보다 오래됐어도 이 변수로 현재 배경과 표면 색을 유지해요. 변수를 사용하지 않는 CSS는 플러그인 작성자가 관리해요.

컴포넌트를 수정하기 전에 갤러리에서 해당 컴포넌트를 사용하는 곳을 모두 확인하세요. .necto-body는 산문 페인에는 패딩이 있고 테이블 페인에는 없어요. 같은 클래스라도 콘텐츠에 따라 스타일이 달라요.

원칙 ​

중요한 상태만 강조해요. 평상시 상태는 텍스트 굵기와 차분한 색으로 구분해요. 채도 높은 색은 실패와 가장 눈에 띄어야 하는 항목에만 사용해요.

선택과 포커스는 텍스트 색으로, 스테이터스는 색조로 구분해요. 선택된 행마다 틴트를 칠하지 않고 상태 변화를 알릴 때 색조를 사용해요.

구분자는 하나만 사용해요. 항목은 간격이나 선 중 하나로 구분해요. 행마다 테두리와 호버 배경을 함께 적용하지 마세요.

행 밀도를 통일해요. 모든 화면에서 --necto-row-height를 사용해요.

위계는 타이포그래피가 담당해요. 크기와 선이 할 일을 굵기와 색이 해요. 한 화면에는 보통 세 가지 크기면 충분해요.

UI 서체와 코드 서체는 분리돼 있어요. 레이블과 컨트롤은 UI 서체를, 코드와 값과 플러그인 데이터는 코드 서체를 사용해요. 둘 다 사용자가 설정할 수 있고 같은 선택이 네이티브 셸과 웹 플러그인에 함께 적용돼요.

네 개의 레이어 ​

토큰은 정확히 하나의 레이어에 속하고 각 레이어는 바로 위의 레이어만 읽을 수 있어요.

레이어담는 것예시
Foundation테마별 중립색 단계--necto-base-40
Semantic토큰의 용도--necto-text-secondary
Component위젯에 필요한 크기--necto-row-height
State포커스, 비활성화--necto-focus-ring

플러그인은 semantic과 component 레이어로 스타일을 지정해요. foundation을 직접 사용하면 한 테마에서 맞는 색이 다른 테마에서는 맞지 않을 수 있어요.

두 테마의 색상 단계는 theme.css 상단에서 정의해요. 아래의 테마 규칙은 둘 중 하나를 선택해요. Swift와 WebView 호스트는 필요한 값을 복사해 사용하며 NectoThemeTests에서 네이티브 색상과 실제 WebView에 표시된 색상을 비교해요. 빌드된 패널과 호스트의 토큰 갱신도 검사하며, script/test native로 실행해요.

색 ​

Foundation ​

--necto-base-00부터 --necto-base-100까지예요. 라이트 모드에서 숫자는 밝은색에서 어두운색으로, 다크 모드에서는 어두운색에서 밝은색으로 이어져요. 다른 색상 토큰도 여기서 파생되므로 색상 단계를 바꾸면 시스템 전체에 적용돼요.

Semantic ​

토큰용도
--necto-bg콘텐츠 배경
--necto-sidebar사이드바와 창 크롬
--necto-surface배경 위에 놓이는 패널
--necto-hover호버된 행
--necto-selected선택된 행
--necto-border헤어라인과 구분선
--necto-border-strong입력 테두리, 활성 모서리
--necto-text기본 텍스트
--necto-text-secondary레이블과 메타데이터
--necto-text-tertiary플레이스홀더, 호스트, 타임스탬프
--necto-accent선택과 포커스를 나타내는 텍스트 색
--necto-brand아이덴티티 전용: 앱 아이콘과 브랜드 라인
--necto-success --necto-warning --necto-danger --necto-info스테이터스

--necto-accent는 텍스트 색으로 사용해요. Necto의 오렌지는 --necto-brand로 정의하며 앱 아이콘에만 사용해요. 작업 화면 전체에 오렌지를 쓰면 스테이터스를 색으로 구분하기 어려워요.

색을 리터럴로 쓰지 마세요. 토큰이 없으면 하나 추가해 주세요.

타이포그래피 ​

산문, 레이블, 컨트롤은 UI 서체를, 코드와 데이터는 코드 서체를 사용해요. 호스트의 서체 설정을 따르고 별도의 디스플레이 서체를 번들하지 마세요.

역할크기토큰
Title17px--necto-size-title
Body13px--necto-size-body
Label12px--necto-size-label
Caption11px--necto-size-caption

크기는 사용자의 텍스트 크기 설정을 반영한 --necto-font-scale에서 파생돼요. 하나의 설정으로 셸과 모든 플러그인의 크기를 조절하므로 컴포넌트에 px 폰트 크기를 직접 쓰지 마세요.

Appearance 편집기와 About의 행은 예외예요. 이들은 기본 크기를 유지하고 Appearance는 그 값들을 편집하는 동안 시스템 UI 서체를 사용해요. 설정을 바꾸는 중에 컨트롤의 위치나 서체가 바뀌지 않도록 하는 예외예요. 설정 내비게이션, 플러그인 관리, 로그는 여전히 선택된 텍스트 크기를 따라요.

네이티브 셸은 기본적으로 시스템 언어를 따르고 Settings에서 영어 또는 한국어로 고정할 수 있어요. 웹 플러그인은 문구와 로컬라이제이션을 직접 관리해요. 호스트는 플러그인 코드가 실행되기 전에 선택된 언어를 <html lang>에 쓰고 언어가 바뀌면 열린 패널을 다시 로드해요. 플러그인 콘텐츠를 다시 쓰거나 app-to-desktop 프로토콜에 로케일 오퍼레이션을 추가하지는 않아요.

숫자 컬럼은 --necto-font-mono에 font-variant-numeric: tabular-nums와 오른쪽 정렬을 사용해요. 헤더도 숫자에 맞춰 정렬하면 값을 비교하기 쉬워요.

타임스탬프는 고정 형식이고 로컬라이즈하지 않아요. toLocaleTimeString은 "12시 46분 21초"나 "12:46:21 PM"을 렌더링해 컬럼 너비를 넘고 행 사이의 값을 비교하기 어려워요.

-webkit-font-smoothing은 설정하지 마세요. 플러그인의 텍스트가 AppKit으로 그린 셸 텍스트보다 얇아져요.

간격, 라운딩, 테두리 ​

간격은 4px 그리드를 사용해요. --necto-space-1부터 --necto-space-5까지 (4, 8, 12, 16, 24)예요.

라운딩은 컨트롤과 행에는 --necto-radius-control(4px), 패널에는 --necto-radius-panel(10px)이에요. 중첩된 라운딩은 부모를 넘지 않아요.

테두리는 1px solid var(--necto-border)예요. 영역은 테두리나 표면 색 중 하나로만 구분하고 둘을 함께 쓰면 안 돼요.

컴포넌트 규칙 ​

행은 테두리가 없어요. 호버는 --necto-hover로, 선택은 --necto-selected에 행 시작 부분에 세로선을 더해서 표시해요. 색만으로 선택 상태를 구분하지 않아요.

테이블은 컴팩트한 fixed 레이아웃을 사용하며 줄바꿈 대신 말줄임표로 잘라요. 헤더는 caption 크기와 secondary 색을 쓰고 sticky로 고정해요. 대문자로 바꾸지 않아요.

스테이터스는 작은 사각형과 코드를 함께 표시해요. 200, 404, 실패 모두 같은 방식으로 그려 한 컬럼 안에서 표현을 통일해요. 대기 중인 행은 윤곽선 사각형을 사용해 색조 없이도 구분할 수 있어요.

아이콘은 선으로 그리고 옆의 텍스트에 크기를 맞춰요. SF Symbols는 Mac 폰트예요. 셸은 사용할 수 있지만 플러그인은 인라인 SVG로 직접 그려요. Necto는 브리지로 아이콘 세트를 제공하지 않아요.

연결 상태는 타깃이 연결되어 있으면 채워진 점으로, 끊겼으면 링으로 표시해요. 시뮬레이터도 같은 표시를 사용하고 타깃 종류는 이름 옆의 아이콘으로 구분해요.

버튼은 평소에는 강조하지 않아요. 파괴적인 액션도 일반 텍스트로 표시하고 포인터가 올라갔을 때만 색을 적용해요.

코드 블록은 호버나 키보드 포커스에서 복사 버튼을 표시해요.

빈 상태는 비어 있는 데이터와 다음 동작을 최대 두 줄로 안내해요. 기능을 홍보하지 않아요.

JSON 입력창은 .necto-json-editor를 사용해요. 최소 여섯 행 높이를 유지하고 컨테이너의 높이가 정해져 있으면 남은 공간을 채워요. 레이블과 버튼은 입력창의 스크롤 영역 밖에 두어 데이터를 편집할 공간을 확보하세요.

검색과 필터는 계속 쌓이는 로그와 자주 조회하는 목록에 제공해요. 검색은 이미 메모리에 있는 요약 텍스트를 대상으로 하며 검색용으로 상세 데이터를 가져오지 않아요. 로그 수준, 상태, 종류는 별도 필터로 제공하고 검색어와 함께 적용해요. 실시간 목록이 갱신되어도 입력창의 포커스와 커서 위치를 유지해야 해요.

상세 패널은 기본으로 오른쪽에 열려요. @necto/bridge의 createDetailPane과 공용 .necto-detail 스타일을 사용하세요. 기본적으로 오른쪽에 배치하고 좁은 화면에서는 위아래로 배치해요. 방향별 크기는 플러그인별 키로 저장하고 행 선택·실시간 갱신·패널을 다시 열 때도 유지해요. 테이블은 창 전체가 아닌 패널 너비에 맞춰요.

플러그인 뷰마다 컨트롤러를 하나 만들고 상세 요소를 .necto-app에 추가한 뒤 mount(aside)를 호출하세요. 제거하기 전에는 unmount()를 호출해요. 목록은 .necto-body를 사용하고 .detail-slot으로 감싼다면 display: contents를 적용해요. 구분자는 보조 기술에 방향과 값을 전달하고 화살표 키 조작을 지원해야 해요. 영역이 하나인 화면에는 장식용 구분자를 추가하지 않아요.

스크롤바는 색상 단계에서 scrollbar-color를 받아요. UA에 맡기면 어두운 창에 밝은 띠로 나타나요.

다크 모드 ​

다크 모드는 색을 단순히 반전하지 않아요. 라이트 모드와 반대로 앞에 있는 표면일수록 밝아져요.

두 테마 모두 실제 화면에서 텍스트가 잘 읽히는지 확인하세요. 대비 수치뿐 아니라 장시간 읽을 때의 편안함도 고려해요.

color-scheme은 테마별로 설정돼요. 없으면 UA가 스크롤바, 폼 컨트롤, 캐럿을 시스템 설정에 따라 칠하고 어두운 창 안에서 밝은 채로 남아요.

플러그인에는 테마 코드가 필요 없어요. 호스트가 data-theme으로 창을 고정하고 prefers-color-scheme은 앱 밖에서 열린 플러그인을 위한 fallback이에요. 테마 토글을 추가하지 마세요. 주변 창과 다른 테마를 쓰면 외관이 일치하지 않아요.

접근성 ​

일반 텍스트 대비는 WCAG AA 기준인 4.5:1 이상을 목표로 해요. 배경, 사이드바, 표면, 호버, 선택 상태에서 실제 대비를 계산하세요. 기준에 못 미치면 색상 토큰을 조정하고 같은 토큰을 다른 외관에서도 확인해 주세요.

헤어라인 테두리는 영역을 나누는 용도예요. 정보를 전달할 때는 테두리에만 의존하지 마세요.

색이 단독으로 의미를 전달하는 일은 없어요. 스테이터스 색은 항상 단어, 숫자, 또는 도형과 짝을 이뤄요.

키보드 포커스가 보이도록 :focus-visible의 2px 액센트 아웃라인을 유지하세요. 인터랙티브 행에는 키보드로 접근할 수 있어야 하고 탐색용 리스트에는 화살표 키 이동을 구현해야 해요.

OS의 접근성 설정을 따르세요. Reduce Motion은 트랜지션을 제거하고 상태 변화를 이해하는 데 애니메이션이 필수여서는 안 돼요.

플러그인에서 사용하기 ​

ts
import "@necto/bridge/theme.css";
import "@necto/bridge/components.css";
css
.row {
  height: var(--necto-row-height);
  background: var(--necto-surface);
  border-radius: var(--necto-radius-control);
  color: var(--necto-text);
}

components.css는 테이블, 툴바, 탭, 필드, 버튼, 스테이터스, 배지, 페어, 코드, 빈 상태를 일반 HTML 요소와 CSS로 제공해요. 프레임워크와 관계없이 사용할 수 있으며 별도의 디자인 시스템을 가져올 필요가 없어요.

SwiftUI에서 사용하기 ​

NectoTheme이 같은 값을 담고 있어요.

swift
Text(app.appName)
    .font(.necto(.body))
    .foregroundStyle(NectoTheme.textSecondary)

뷰에서 Color.blue나 원시 hex를 쓰지 마세요. 토큰이 없으면 먼저 theme.css에 추가한 뒤 Swift에도 같은 값을 반영해 주세요. 디자인 토큰 하네스로 두 값을 비교해요.

이 가이드가 다루는 범위 ​

Necto가 관리하는 UI인 Necto/, WebPackages/Bridge/, WebPackages/BuiltInPlugins/src/는 이 가이드를 따라야 해요. 외부 플러그인은 다른 스타일을 사용해도 돼요. 브리지는 스타일을 검사하거나 외관을 이유로 플러그인 로드를 거부하지 않아요.