npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@juchan/jds

v1.0.0

Published

![header](https://capsule-render.vercel.app/api?type=rect&color=0:EEFF00,100:a82da8&fontColor=FFFFFF&height=100&section=header&text=Juchan%20Design%20System%20Library&fontSize=40&fontAlign=50&fontAlignY=50)

Readme

header

Node.js Pnpm React Typescript Tailwindcss Vite

순수 headless 컴포넌트 라이브러리입니다. 설치해도 아무 스타일이 없습니다.

버그가 아니라 설계입니다. 라이브러리는 동작 · 접근성 배선 · data-* 상태 노출만 책임지고 모양은 전부 소비자의 것입니다. 화면에 아무것도 보이지 않는 상태에서 시작해 className으로 채워 나가는 것이 정상 흐름입니다.

바로 붙여 쓸 수 있는 완성 스니펫은 Storybook의 Recipes/ 섹션에 있습니다.

📚 Deployment

  • 항상 release branch의 최신 변경 사항을 반영합니다.

Package

  • Link: https://www.npmjs.com/package/@juchan/jds

pnpm

 pnpm add @juchan/jds

npm

 npm install @juchan/jds

Storybook

  • Link: https://juchanhwang.github.io/jds/

설치와 CSS import

패키지만 설치하면 절반입니다. CSS를 import하지 않으면 토큰 유틸리티가 생성되지 않고, 에러도 없이 스타일만 조용히 빠집니다.

Tailwind CSS v4 소비자

@import "tailwindcss";
@import "@juchan/jds/theme.css";

theme.csstokens.css를 함께 끌어오므로 이 두 줄이면 됩니다. bg-primary-60 · text-b2 · text-gray-80 같은 유틸리티가 생성됩니다.

Tailwind를 쓰지 않는 소비자

@import "@juchan/jds/tokens.css";

CSS 변수만 들어옵니다. var(--jds-color-primary-60) 형태로 직접 참조하세요.

peerDependencies

React 18 또는 19 (react, react-dom). 런타임 의존성은 없습니다.

컴포넌트

7종입니다. 훅 · 아이콘 · 내부 유틸리티는 공개하지 않습니다.

| 컴포넌트 | 루트 DOM | 비고 | | --- | --- | --- | | Button | <button> (as로 변경 가능) | 다형성. type="button"이 기본값 | | Checkbox | <button role="checkbox"> + hidden input | indeterminate 지원, Checkbox.Indicator | | Radio | <input type="radio"> | 단일 요소. native name 그룹핑 | | Switch | <button role="switch"> + hidden input | Switch.Thumb | | TextInput | <div> 래퍼 | 에러 · 초기화 버튼 | | InputField | <div> 래퍼 | 라벨 슬롯 · readOnly | | Select | context (자체 DOM 없음) | Select.Trigger / .Content / .Option |

최소 사용 예시입니다. 클래스는 전부 소비자 몫이라 아래에는 하나도 없습니다 — 그래서 이대로 렌더하면 아무것도 보이지 않습니다.

import { Button, Checkbox, Select, type SelectOption } from '@juchan/jds';

const options: SelectOption[] = [
  { label: 'React', value: 'react' },
  { label: 'Vue', value: 'vue' },
];

<Button onClick={submit}>제출</Button>;

<Checkbox name="agree" aria-label="동의">
  <Checkbox.Indicator>✓</Checkbox.Indicator>
</Checkbox>;

<Select options={options} name="framework">
  <Select.Trigger>선택</Select.Trigger>
  <Select.Content>
    {options.map((option, index) => (
      <Select.Option key={option.value} index={index}>
        {option.label}
      </Select.Option>
    ))}
  </Select.Content>
</Select>;

공개 타입은 CheckedState · SelectOption · ButtonProps 세 가지입니다.

규약

data-* 상태

순수 headless에서 data-*는 소비자가 스타일을 걸 수 있는 유일한 상태 채널입니다. 불리언형은 값이 없는 presence이고, 열거형은 data-state 하나로 통일됩니다.

| 컴포넌트 | 속성 | | --- | --- | | Button | data-disabled | | Checkbox | data-state(checked | unchecked | indeterminate) · data-disabled | | Radio · Switch | data-state(checked | unchecked) · data-disabled | | TextInput | data-disabled · data-invalid | | InputField | data-disabled · data-invalid · data-readonly | | Select | Trigger data-state(open | closed) · data-disabled · data-placeholder / Option data-state · data-highlighted · data-disabled |

폼 참여용 hidden input에는 data-*를 붙이지 않습니다. 상세는 docs/conventions/data-attributes.md.

prop 합성 순서

소비자 핸들러를 먼저 호출하고, 소비자가 event.preventDefault()를 호출했으면 내부 동작을 스킵합니다. 기본 동작을 끄는 공식 통로입니다.

ARIA · role · data-*는 소비자 props 스프레드 에 있어 필요하면 덮어쓸 수 있습니다. 핸들러와 ref는 스프레드 에서 합성해 유실되지 않습니다.

예외: disabled 상태에서는 소비자 핸들러도 호출하지 않습니다. native <button disabled>의 의미론을 비-native 요소까지 일관 적용합니다. 상세는 docs/conventions/prop-composition.md.

forwardRef

7종 전부 ref를 전달합니다. SelectTrigger · Content · Option이 각각 받습니다(루트는 자체 DOM이 없습니다). TextInput · InputFieldref는 래퍼 <div>가 아니라 **<input>**을 가리킵니다.

색 대비

흰 배경 위 전경색은 팔레트별 최소 scale 이상만 쓰세요 — primary 50 · error 50 · warning 70 · blue 60 · green 70 · gray 60 (WCAG 2.2 AA). 상세는 docs/conventions/color-contrast.md.

다크 모드

v1은 다크 값을 배포하지 않습니다. .dark 스코프에서 --jds-color-*를 재정의하면 @theme inline 덕분에 유틸리티가 그대로 따라옵니다.

.dark {
  --jds-color-gray-10: #1f2937;
  --jds-color-gray-80: #e5e7eb;
}

v1.0.0 breaking changes

0.9.x에서 올라올 때 동작이 바뀌는 지점입니다.

  1. 비제어 모드에서 onChange 계열 콜백이 호출되기 시작합니다. SelectonValueChange / onOpenChange, CheckboxonCheckedChange가 대상입니다. 이전에는 비제어에서 호출되지 않아 변경을 감지할 수 없었습니다. 콜백 안에서 부수효과를 돌리고 있었다면 호출 횟수를 확인하세요.

  2. prop 합성 순서가 통일됩니다. 소비자 핸들러가 먼저 실행되고 event.defaultPrevented면 내부 동작이 스킵됩니다. 이전에는 컴포넌트마다 소비자와 내부 중 한쪽이 다른 쪽을 통째로 덮어썼습니다 — Checkbox는 소비자 onClick이 내부 토글을 죽였고, Button은 소비자 onClick이 무시됐습니다.

  3. Select에서 닫힌 상태의 ArrowUp / ArrowDown이 목록을 엽니다. 이전에는 보이지 않는 강조 인덱스만 움직여 키보드 사용자에게는 아무 반응이 없었습니다.

  4. data-* 속성 규약이 확정됩니다. 불리언형은 '' 또는 속성 부재로 표현합니다("true" 문자열이 아닙니다). Checkbox.Indicatordata-state가 root와 같은 3값 도메인이 됩니다. 이전 값 도메인에 의존한 CSS는 갱신이 필요합니다.

  5. Checkbox의 hidden input이 더 이상 탭 스톱이 아닙니다. 이전에는 Tab을 누르면 보이지 않는 input에 포커스가 갔습니다. 버그 수정이지만 폼의 탭 순서가 바뀝니다.

알아둘 동작

  • indeterminate는 폼 제출 시 checked=false로 나갑니다. hidden input의 .indeterminate DOM 프로퍼티는 제출값을 바꾸지 않습니다. "부분 선택"을 서버로 보내야 한다면 별도 필드를 쓰세요.
  • Radio만 DOM 구조가 다릅니다. Checkbox · Switch<button role="..."> 루트인 것과 달리 <input type="radio"> 단일 요소입니다. RadioGroup이 v1 스코프 밖이라 배타 선택을 브라우저의 native name 그룹핑에 맡기기 때문입니다. <button role="radio">로 가면 roving focus와 배타성을 직접 구현해야 하고, 그것이 곧 RadioGroup입니다.
  • Select.Contentdocument.body로 portal됩니다. 부모의 overflow에 잘리지 않는 대신 z-index는 소비자가 정해야 합니다.
  • Select의 강조 이동은 일부 스크린리더에서 자동 안내되지 않을 수 있습니다. 목록이 열려도 DOM 포커스가 트리거에 남는 설계라, listbox에 부여된 aria-activedescendant가 보조기술에 전달되지 않을 수 있습니다. 완전한 포커스 관리(WAI-ARIA APG combobox 패턴)는 v1 스코프 밖이며 후속 버전에서 다룹니다.
  • TextInput · InputField의 초기화 버튼은 제어 모드에서만 나타납니다. 내부 상태를 두지 않아 비제어 입력의 현재 값을 알 수 없습니다.