Top Navigation 제품 상단 바입니다. 제품 로고 · 1차 메뉴(사이드 내비가 없는 제품) · 전역 검색 · 알림 · 사용자 메뉴로 구성합니다.
1. Default 로고 + 메뉴 + 우측 액션. 현재 메뉴는 .on.
HTML
< header class = "topbar" style = "border-radius:var(--radius-lg);border:1px solid var(--border-default)" >
< a href = "#" class = "logo" > < i > </ i > Security Console < small > v2.4</ small > </ a >
< nav aria-label = "주 메뉴" > < a href = "#" class = "on" aria-current = "page" > 대시보드</ a > < a href = "#" > 이벤트</ a > < a href = "#" > 정책</ a > < a href = "#" > 에이전트</ a > </ nav >
< div class = "right" >
< span class = "with-count" > < button type = "button" class = "btn sm tertiary icon" aria-label = "알림 3건" > < svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" width = "18" height = "18" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" > < path d = "M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2 -3v-3a7 7 0 0 1 4 -6" /> < path d = "M9 17v1a3 3 0 0 0 6 0v-1" /> </ svg > </ button > < span class = "count" > 3</ span > </ span >
< button type = "button" class = "btn sm tertiary icon" aria-label = "설정" > < svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" width = "18" height = "18" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" > < path d = "M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065" /> < path d = "M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" /> </ svg > </ button >
< span class = "divider vertical" role = "separator" aria-orientation = "vertical" > </ span >
< span class = "avatar" title = "정희 · 관리자" > JH</ span >
</ div >
</ header > CSS · .topbar, .avatar, .searchbar, .divider, .count, .with-count CSS 복사
/* JS Design System v0.6 · .topbar, .avatar, .searchbar, .divider, .count, .with-count · 토큰(var(--*))은 assets/style.css :root 참고 */
.searchbar { display : flex ; align-items : center ; gap : 8px ; height : 40px ; padding : 0 12px ; min-width : 280px ; border : 1px solid var(--border-strong) ; border-radius : var(--radius-md) ; background : var(--bg-surface) ; transition : border-color var(--motion-fast) }
.searchbar svg { width : 18px ; height : 18px ; color : var(--text-tertiary) ; flex : none }
.searchbar input { flex : 1 ; border : 0 ; background : transparent ; font : inherit ; font-size : 14px ; color : var(--text-primary) ; outline : 0 ; min-width : 0 }
.searchbar input::placeholder { color : var(--text-disabled) }
.searchbar:focus-within { border-color : var(--border-focus) }
.searchbar .clear { border : 0 ; padding : 0 ; background : var(--bg-panel) ; color : var(--text-secondary) ; width : 20px ; height : 20px ; border-radius : 50% ; font : inherit ; font-size : 12px ; line-height : 0 ; cursor : pointer ; display : grid ; place-items : center ; flex : none }
.searchbar .clear svg { display : block ; width : 12px ; height : 12px }
.searchbar kbd { font-size : 12px ; color : var(--text-tertiary) ; border : 1px solid var(--border-default) ; border-radius : 4px ; padding : 1px 5px ; background : var(--bg-subtle) }
.searchbar.lg { height : 48px }
.searchbar.lg input { font-size : 16px }
.searchbar.block { width : 100% }
.tabs .tab .count { font-size : 12px ; font-weight : 700 ; padding : 1px 6px ; border-radius : var(--radius-full) ; background : var(--bg-panel) ; color : var(--text-secondary) ; min-width : 0 ; height : auto }
.tabs .tab.on .count { background : var(--accent-subtle) ; color : var(--accent-text) }
.sidenav a .count { margin-left : auto }
.sidenav.collapsed a span,.sidenav.collapsed .group,.sidenav.collapsed a .count { display : none }
.topbar { display : flex ; align-items : center ; gap : 16px ; height : 56px ; padding : 0 20px ; background : var(--bg-surface) ; border-bottom : 1px solid var(--border-default) ; width : 100% }
.topbar .logo { font-weight : 700 ; display : flex ; align-items : center ; gap : 8px ; text-decoration : none ; color : var(--text-primary) ; white-space : nowrap }
.topbar .logo i { width : 24px ; height : 24px ; border-radius : 6px ; background : var(--accent) ; display : inline-block }
.topbar .logo small { font-weight : 500 ; color : var(--text-tertiary) ; font-size : 12px }
.topbar nav { display : flex ; gap : 4px ; margin-left : 8px }
.topbar nav a { padding : 8px 12px ; border-radius : var(--radius-md) ; text-decoration : none ; color : var(--text-secondary) ; font-size : 14px ; font-weight : 500 ; white-space : nowrap }
.topbar nav a:hover { color : var(--text-primary) }
.topbar nav a.on { color : var(--text-primary) ; background : var(--bg-panel) }
.topbar .right { margin-left : auto ; display : flex ; align-items : center ; gap : 8px }
.topbar .searchbar { min-width : 220px ; height : 36px }
.topbar .divider.vertical { height : 20px }
.avatar { width : 32px ; height : 32px ; border-radius : 50% ; background : var(--accent-subtle) ; color : var(--accent-text) ; display : inline-grid ; place-items : center ; font-size : 12px ; font-weight : 700 ; flex : none }
.avatar.sm { width : 24px ; height : 24px ; font-size : 10px }
.count { display : inline-grid ; place-items : center ; min-width : 20px ; height : 20px ; padding : 0 6px ; border-radius : var(--radius-full) ; background : var(--danger) ; color : var(--danger-on) ; font-size : 12px ; font-weight : 700 ; line-height : 1 ; }
.count.neutral { background : var(--bg-panel) ; color : var(--text-secondary) }
.count.accent { background : var(--accent) ; color : var(--accent-on) }
.count.dot { min-width : 8px ; width : 8px ; height : 8px ; padding : 0 }
.with-count { position : relative ; display : inline-flex }
.with-count .count { position : absolute ; top : -6px ; right : -6px }
.divider { border : 0 ; border-top : 1px solid var(--border-default) ; margin : 16px 0 ; width : 100% }
.divider.strong { border-color : var(--border-strong) }
.divider.dashed { border-top-style : dashed }
.divider.label { display : flex ; align-items : center ; gap : 12px ; border : 0 ; font-size : 12px ; color : var(--text-tertiary) ; white-space : nowrap }
.divider.label::before,.divider.label::after { content : "" ; flex : 1 ; height : 1px ; background : var(--border-default) }
.divider.vertical { width : 1px ; height : 24px ; border : 0 ; background : var(--border-default) ; margin : 0 12px ; display : inline-block ; vertical-align : middle ; flex : none }
React · @jiran/ds-react
import { Avatar, Button, Count, Divider, Icon, TopBar, TopBarLogo, TopBarNav, TopBarNavItem, TopBarRight, WithCount } from "@jiran/ds-react" ;
export default function Example() {
return (
< TopBar style ={{ borderRadius: "var(--radius-lg)" , border: "1px solid var(--border-default)" }}>
< TopBarLogo href ="#" name ="Security Console" version ="v2.4" />
< TopBarNav >
< TopBarNavItem href ="#" active> 대시보드</ TopBarNavItem >
< TopBarNavItem href ="#" > 이벤트</ TopBarNavItem >
< TopBarNavItem href ="#" > 정책</ TopBarNavItem >
< TopBarNavItem href ="#" > 에이전트</ TopBarNavItem >
</ TopBarNav >
< TopBarRight >
< WithCount count ={< Count value ={3} /> }>
< Button size ="sm" variant ="tertiary" icon aria-label ="알림 3건" > < Icon name ="bell" /> </ Button >
</ WithCount >
< Button size ="sm" variant ="tertiary" icon aria-label ="설정" > < Icon name ="settings" /> </ Button >
< Divider orientation ="vertical" />
< Avatar initials ="JH" title ="정희 · 관리자" />
</ TopBarRight >
</ TopBar >
);
}
2. With Search 사이드 내비가 있는 제품은 상단 메뉴 대신 전역 검색을 둡니다.
HTML
< header class = "topbar" style = "border-radius:var(--radius-lg);border:1px solid var(--border-default)" >
< a href = "#" class = "logo" > < i > </ i > Security Console</ a >
< div class = "searchbar" role = "search" style = "margin-left:16px" > < svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" width = "18" height = "18" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" > < path d = "M3 10a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" /> < path d = "M21 21l-6 -6" /> </ svg > < input type = "search" placeholder = "이벤트, 대상, IP 검색" aria-label = "전역 검색" > < kbd > /</ kbd > </ div >
< div class = "right" >
< span class = "with-count" > < button type = "button" class = "btn sm tertiary icon" aria-label = "알림" > < svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" width = "18" height = "18" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" > < path d = "M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2 -3v-3a7 7 0 0 1 4 -6" /> < path d = "M9 17v1a3 3 0 0 0 6 0v-1" /> </ svg > </ button > < span class = "count dot" aria-hidden = "true" > </ span > </ span >
< span class = "avatar" > JH</ span >
</ div >
</ header > CSS · .topbar, .avatar, .searchbar, .divider, .count, .with-count CSS 복사
/* JS Design System v0.6 · .topbar, .avatar, .searchbar, .divider, .count, .with-count · 토큰(var(--*))은 assets/style.css :root 참고 */
.searchbar { display : flex ; align-items : center ; gap : 8px ; height : 40px ; padding : 0 12px ; min-width : 280px ; border : 1px solid var(--border-strong) ; border-radius : var(--radius-md) ; background : var(--bg-surface) ; transition : border-color var(--motion-fast) }
.searchbar svg { width : 18px ; height : 18px ; color : var(--text-tertiary) ; flex : none }
.searchbar input { flex : 1 ; border : 0 ; background : transparent ; font : inherit ; font-size : 14px ; color : var(--text-primary) ; outline : 0 ; min-width : 0 }
.searchbar input::placeholder { color : var(--text-disabled) }
.searchbar:focus-within { border-color : var(--border-focus) }
.searchbar .clear { border : 0 ; padding : 0 ; background : var(--bg-panel) ; color : var(--text-secondary) ; width : 20px ; height : 20px ; border-radius : 50% ; font : inherit ; font-size : 12px ; line-height : 0 ; cursor : pointer ; display : grid ; place-items : center ; flex : none }
.searchbar .clear svg { display : block ; width : 12px ; height : 12px }
.searchbar kbd { font-size : 12px ; color : var(--text-tertiary) ; border : 1px solid var(--border-default) ; border-radius : 4px ; padding : 1px 5px ; background : var(--bg-subtle) }
.searchbar.lg { height : 48px }
.searchbar.lg input { font-size : 16px }
.searchbar.block { width : 100% }
.tabs .tab .count { font-size : 12px ; font-weight : 700 ; padding : 1px 6px ; border-radius : var(--radius-full) ; background : var(--bg-panel) ; color : var(--text-secondary) ; min-width : 0 ; height : auto }
.tabs .tab.on .count { background : var(--accent-subtle) ; color : var(--accent-text) }
.sidenav a .count { margin-left : auto }
.sidenav.collapsed a span,.sidenav.collapsed .group,.sidenav.collapsed a .count { display : none }
.topbar { display : flex ; align-items : center ; gap : 16px ; height : 56px ; padding : 0 20px ; background : var(--bg-surface) ; border-bottom : 1px solid var(--border-default) ; width : 100% }
.topbar .logo { font-weight : 700 ; display : flex ; align-items : center ; gap : 8px ; text-decoration : none ; color : var(--text-primary) ; white-space : nowrap }
.topbar .logo i { width : 24px ; height : 24px ; border-radius : 6px ; background : var(--accent) ; display : inline-block }
.topbar .logo small { font-weight : 500 ; color : var(--text-tertiary) ; font-size : 12px }
.topbar nav { display : flex ; gap : 4px ; margin-left : 8px }
.topbar nav a { padding : 8px 12px ; border-radius : var(--radius-md) ; text-decoration : none ; color : var(--text-secondary) ; font-size : 14px ; font-weight : 500 ; white-space : nowrap }
.topbar nav a:hover { color : var(--text-primary) }
.topbar nav a.on { color : var(--text-primary) ; background : var(--bg-panel) }
.topbar .right { margin-left : auto ; display : flex ; align-items : center ; gap : 8px }
.topbar .searchbar { min-width : 220px ; height : 36px }
.topbar .divider.vertical { height : 20px }
.avatar { width : 32px ; height : 32px ; border-radius : 50% ; background : var(--accent-subtle) ; color : var(--accent-text) ; display : inline-grid ; place-items : center ; font-size : 12px ; font-weight : 700 ; flex : none }
.avatar.sm { width : 24px ; height : 24px ; font-size : 10px }
.count { display : inline-grid ; place-items : center ; min-width : 20px ; height : 20px ; padding : 0 6px ; border-radius : var(--radius-full) ; background : var(--danger) ; color : var(--danger-on) ; font-size : 12px ; font-weight : 700 ; line-height : 1 ; }
.count.neutral { background : var(--bg-panel) ; color : var(--text-secondary) }
.count.accent { background : var(--accent) ; color : var(--accent-on) }
.count.dot { min-width : 8px ; width : 8px ; height : 8px ; padding : 0 }
.with-count { position : relative ; display : inline-flex }
.with-count .count { position : absolute ; top : -6px ; right : -6px }
.divider { border : 0 ; border-top : 1px solid var(--border-default) ; margin : 16px 0 ; width : 100% }
.divider.strong { border-color : var(--border-strong) }
.divider.dashed { border-top-style : dashed }
.divider.label { display : flex ; align-items : center ; gap : 12px ; border : 0 ; font-size : 12px ; color : var(--text-tertiary) ; white-space : nowrap }
.divider.label::before,.divider.label::after { content : "" ; flex : 1 ; height : 1px ; background : var(--border-default) }
.divider.vertical { width : 1px ; height : 24px ; border : 0 ; background : var(--border-default) ; margin : 0 12px ; display : inline-block ; vertical-align : middle ; flex : none }
React · @jiran/ds-react
import { Avatar, Button, Count, Icon, SearchBar, TopBar, TopBarLogo, TopBarRight, WithCount } from "@jiran/ds-react" ;
export default function Example() {
return (
< TopBar style ={{ borderRadius: "var(--radius-lg)" , border: "1px solid var(--border-default)" }}>
< TopBarLogo href ="#" name ="Security Console" />
< SearchBar placeholder ="이벤트, 대상, IP 검색" label ="전역 검색" shortcut ="/" hotkey wrapperStyle ={{ marginLeft: 16 }} />
< TopBarRight >
< WithCount count ={< Count dot /> }>
< Button size ="sm" variant ="tertiary" icon aria-label ="알림" > < Icon name ="bell" /> </ Button >
</ WithCount >
< Avatar initials ="JH" />
</ TopBarRight >
</ TopBar >
);
}
Props HTML 에서는 클래스와 속성으로, React(@jiran/ds-react)에서는 같은 이름의 prop(variant · size …)으로 변형·상태를 지정합니다.
Prop / Class Type · Values Default Description .topbar header—높이 56, 하단 1px .logo a—제품 마크(i) + 이름 + small 버전 nav a.on current—현재 1차 메뉴 .right container—우측 액션(알림·설정·아바타) .avatar span—이니셜 32 (sm 24)
Usage React : @jiran/ds-react 패키지를 설치하고 style.css 를 한 번 import 하면 위 React 탭 코드를 그대로 쓸 수 있습니다(React Package ). HTML : assets/style.css 를 연결하고(일부만 필요하면 아래 컴포넌트 CSS 복사), 동작이 필요하면 assets/ds.js 를 함께 넣습니다 — 이 페이지의 프리뷰가 그 스크립트로 동작합니다. 색은 전부 토큰 (var(--accent) 등)을 참조하므로 --accent-* 만 제품 컬러로 바꾸면 됩니다.
React @jiran/ds-react 복사 npm i @jiran/ds-react
// main.tsx — 앱 진입점에서 한 번
import "@jiran/ds-react/style.css" ;
// 컴포넌트
import { TopBar, TopBarLogo, TopBarNav, TopBarNavItem, TopBarRight, Avatar } from "@jiran/ds-react" ;
HTML 복사 <!-- 1) 토큰 + 전체 컴포넌트 스타일 한 번에 연결 -->
< link rel = "stylesheet" href = "assets/style.css" >
<!-- 동작(드롭다운·탭·달력·표 정렬/선택·토스트 등) — 순수 JS 한 파일. React 제품은 불필요 -->
< script src = "assets/ds.js" defer > </ script >
<!-- 2) 또는 아래 "Component CSS" 만 프로젝트 CSS 에 붙이고,
:root 토큰은 Foundations › Design Token 에서 복사 -->
Component CSS .topbar, .avatar, .searchbar, .divider, .count, .with-count CSS 복사 /* JS Design System v0.6 · .topbar, .avatar, .searchbar, .divider, .count, .with-count · 토큰(var(--*))은 assets/style.css :root 참고 */
.searchbar { display : flex ; align-items : center ; gap : 8px ; height : 40px ; padding : 0 12px ; min-width : 280px ; border : 1px solid var(--border-strong) ; border-radius : var(--radius-md) ; background : var(--bg-surface) ; transition : border-color var(--motion-fast) }
.searchbar svg { width : 18px ; height : 18px ; color : var(--text-tertiary) ; flex : none }
.searchbar input { flex : 1 ; border : 0 ; background : transparent ; font : inherit ; font-size : 14px ; color : var(--text-primary) ; outline : 0 ; min-width : 0 }
.searchbar input::placeholder { color : var(--text-disabled) }
.searchbar:focus-within { border-color : var(--border-focus) }
.searchbar .clear { border : 0 ; padding : 0 ; background : var(--bg-panel) ; color : var(--text-secondary) ; width : 20px ; height : 20px ; border-radius : 50% ; font : inherit ; font-size : 12px ; line-height : 0 ; cursor : pointer ; display : grid ; place-items : center ; flex : none }
.searchbar .clear svg { display : block ; width : 12px ; height : 12px }
.searchbar kbd { font-size : 12px ; color : var(--text-tertiary) ; border : 1px solid var(--border-default) ; border-radius : 4px ; padding : 1px 5px ; background : var(--bg-subtle) }
.searchbar.lg { height : 48px }
.searchbar.lg input { font-size : 16px }
.searchbar.block { width : 100% }
.tabs .tab .count { font-size : 12px ; font-weight : 700 ; padding : 1px 6px ; border-radius : var(--radius-full) ; background : var(--bg-panel) ; color : var(--text-secondary) ; min-width : 0 ; height : auto }
.tabs .tab.on .count { background : var(--accent-subtle) ; color : var(--accent-text) }
.sidenav a .count { margin-left : auto }
.sidenav.collapsed a span,.sidenav.collapsed .group,.sidenav.collapsed a .count { display : none }
.topbar { display : flex ; align-items : center ; gap : 16px ; height : 56px ; padding : 0 20px ; background : var(--bg-surface) ; border-bottom : 1px solid var(--border-default) ; width : 100% }
.topbar .logo { font-weight : 700 ; display : flex ; align-items : center ; gap : 8px ; text-decoration : none ; color : var(--text-primary) ; white-space : nowrap }
.topbar .logo i { width : 24px ; height : 24px ; border-radius : 6px ; background : var(--accent) ; display : inline-block }
.topbar .logo small { font-weight : 500 ; color : var(--text-tertiary) ; font-size : 12px }
.topbar nav { display : flex ; gap : 4px ; margin-left : 8px }
.topbar nav a { padding : 8px 12px ; border-radius : var(--radius-md) ; text-decoration : none ; color : var(--text-secondary) ; font-size : 14px ; font-weight : 500 ; white-space : nowrap }
.topbar nav a:hover { color : var(--text-primary) }
.topbar nav a.on { color : var(--text-primary) ; background : var(--bg-panel) }
.topbar .right { margin-left : auto ; display : flex ; align-items : center ; gap : 8px }
.topbar .searchbar { min-width : 220px ; height : 36px }
.topbar .divider.vertical { height : 20px }
.avatar { width : 32px ; height : 32px ; border-radius : 50% ; background : var(--accent-subtle) ; color : var(--accent-text) ; display : inline-grid ; place-items : center ; font-size : 12px ; font-weight : 700 ; flex : none }
.avatar.sm { width : 24px ; height : 24px ; font-size : 10px }
.count { display : inline-grid ; place-items : center ; min-width : 20px ; height : 20px ; padding : 0 6px ; border-radius : var(--radius-full) ; background : var(--danger) ; color : var(--danger-on) ; font-size : 12px ; font-weight : 700 ; line-height : 1 ; }
.count.neutral { background : var(--bg-panel) ; color : var(--text-secondary) }
.count.accent { background : var(--accent) ; color : var(--accent-on) }
.count.dot { min-width : 8px ; width : 8px ; height : 8px ; padding : 0 }
.with-count { position : relative ; display : inline-flex }
.with-count .count { position : absolute ; top : -6px ; right : -6px }
.divider { border : 0 ; border-top : 1px solid var(--border-default) ; margin : 16px 0 ; width : 100% }
.divider.strong { border-color : var(--border-strong) }
.divider.dashed { border-top-style : dashed }
.divider.label { display : flex ; align-items : center ; gap : 12px ; border : 0 ; font-size : 12px ; color : var(--text-tertiary) ; white-space : nowrap }
.divider.label::before,.divider.label::after { content : "" ; flex : 1 ; height : 1px ; background : var(--border-default) }
.divider.vertical { width : 1px ; height : 24px ; border : 0 ; background : var(--border-default) ; margin : 0 12px ; display : inline-block ; vertical-align : middle ; flex : none }