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

@ngsk2784-lab/ui

v0.1.3

Published

React component + pure-CSS-token UI kit with 3 swappable skins (Dark Depth / Korean Service Light / Neo Pixel).

Readme

@ngsk2784-lab/ui

React 컴포넌트 라이브러리 + 순수 CSS 토큰. 3가지 스킨(Dark Depth / Korean Service Light / Neo Pixel)으로 빠르게 UI를 구축하세요.

주요 특징

  • 토큰 기반 디자인: Semantic 토큰 30종으로 스킨 전환도 간편
  • 3 스킨 지원
    • Dark Depth: 도구·대시보드 (앰버 톤, 예리한 모서리)
    • Korean Service Light: 대중 서비스 (따뜻한 톤, 부드러운 그림자)
    • Neo Pixel: 픽셀 게임 (라임/마젠타, 하드엣지)
  • 접근성 내장: WCAG 대비, 포커스 링, 44px 히트영역, 컬러 + 아이콘 병행
  • 모션 기본값: 눌림/호버/진입 애니메이션 자동 적용
  • Radix Primitives 기반: 복잡한 접근성·키보드 네비는 Radix가 담당, 스킨은 우리가

설치

npm install @ngsk2784-lab/ui
# 또는
pnpm add @ngsk2784-lab/ui

피어 의존성 (반드시 설치):

npm install react react-dom

React 18, 19 모두 지원.

빠른 시작 (10분)

1단계: CSS import

반드시 이 순서대로 import하세요:

// 1. 기본 토큰 (필수)
import '@ngsk2784-lab/ui/base.css';

// 2. 스킨 선택 (3개 중 1개)
import '@ngsk2784-lab/ui/skins/dark-depth.css';
// import '@ngsk2784-lab/ui/skins/korean-service-light.css';
// import '@ngsk2784-lab/ui/skins/neo-pixel.css';

// 3. 컴포넌트 스타일 (필수)
import '@ngsk2784-lab/ui/index.css';

또는 한 줄로 번들 import (기본값 = Dark Depth):

import '@ngsk2784-lab/ui/styles.css';

2단계: UIProvider 감싸기

앱의 최상위에서:

import { UIProvider, Button } from '@ngsk2784-lab/ui';

export default function App() {
  return (
    <UIProvider skin="dark-depth">
      <YourApp />
    </UIProvider>
  );
}

3단계: 컴포넌트 사용

import { Button, Input, Field } from '@ngsk2784-lab/ui';

export function LoginForm() {
  const [email, setEmail] = useState('');

  return (
    <form>
      <Field label="이메일" required>
        <Input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="[email protected]"
        />
      </Field>
      <Button variant="primary" size="md">
        로그인
      </Button>
    </form>
  );
}

스킨 전환

앱 렌더링 후 스킨을 바꾸려면 data-skin 속성만 변경:

const [skin, setSkin] = useState('dark-depth');

<UIProvider skin={skin}>
  {/* 모든 자식이 자동으로 리스킨됨 */}
</UIProvider>

// 또는 document 직접 변경 (Provider 없을 때)
document.documentElement.dataset.skin = 'korean-service-light';

컴포넌트 (18종)

| 카테고리 | 컴포넌트 | 용도 | |---------|---------|------| | | Button | 액션, 3종 스타일 (primary/ghost/danger) | | | Input | 단일행 텍스트 입력 | | | Textarea | 다중행 텍스트 | | | Select | 드롭다운 선택 | | | Checkbox | 다중 선택 (체크박스) | | | RadioGroup | 단일 선택 (라디오) | | | Switch | 토글 스위치 | | | Field | 라벨·헬퍼·에러 일관 배치 | | | FormRow | 필드 가로 배치 (반응형) | | 오버레이 & 피드백 | Modal | 다이얼로그, focus trap + Esc 닫기 | | | toast() | 스낵바 (success/error/info) | | | Badge | 상태 태그 (neutral/ok/warn/bad/info) | | | Spinner | 로딩 표시 (원형 회전) | | | Skeleton | 콘텐츠 로딩 placeholder (레이아웃 유지) | | 레이아웃 & 표시 | Card | 슬롯 기반 카드 (호버 리프트) | | | Table | 데이터 테이블 + 페이지네이션 | | | Tabs | 탭 네비게이션 | | | Segmented | 선택 알약 (라디오형) |

사용 예시

1. 폼 & 검증

import { useForm } from 'react-hook-form';
import { Field, Input, Button, toast } from '@ngsk2784-lab/ui';

export function SignupForm() {
  const { register, handleSubmit, formState: { errors } } = useForm();

  const onSubmit = async (data) => {
    try {
      await api.signup(data);
      toast({
        type: 'success',
        title: '가입 완료',
        description: '로그인 페이지로 이동합니다.',
      });
    } catch (err) {
      toast({
        type: 'error',
        title: '가입 실패',
        description: err.message,
      });
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Field label="이름" required error={errors.name?.message}>
        <Input {...register('name', { required: '필수 입력' })} />
      </Field>
      <Button variant="primary" type="submit">
        가입하기
      </Button>
    </form>
  );
}

2. 모달 다이얼로그

import { Modal, Button } from '@ngsk2784-lab/ui';
import { useState } from 'react';

export function DeleteModal() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Button variant="danger" onClick={() => setOpen(true)}>
        삭제
      </Button>
      <Modal
        open={open}
        onOpenChange={setOpen}
        title="정말 삭제하시겠어요?"
        size="sm"
      >
        <p>이 작업은 되돌릴 수 없습니다.</p>
        <Modal.Footer>
          <Button variant="ghost" onClick={() => setOpen(false)}>
            취소
          </Button>
          <Button variant="danger" onClick={() => {
            // 삭제 로직
            setOpen(false);
          }}>
            삭제
          </Button>
        </Modal.Footer>
      </Modal>
    </>
  );
}

3. 토스트 알림

import { toast, useToast, ToastViewport } from '@ngsk2784-lab/ui';

export function App() {
  return (
    <>
      {/* 앱 최상위에 한 번만 */}
      <ToastViewport />

      <button onClick={() => {
        toast({
          type: 'success',
          title: '저장됨',
          description: '변경사항이 저장되었습니다.',
        });
      }}>
        저장
      </button>
    </>
  );
}

// 또는 hook 사용
export function Component() {
  const { toast } = useToast();

  return (
    <button onClick={() => toast({ type: 'info', title: '알림' })}>
      알림 보기
    </button>
  );
}

디자인 파운데이션

모든 컴포넌트는 아래를 자동으로 준수합니다:

  • 8pt 그리드 & 스페이싱: 토큰화된 간격 (--space-1 ~ --space-7)
  • 색 대비: WCAG AA 이상 (4.5:1 본문, 3:1 UI)
  • 포커스 시각화: 2px 아웃라인 + glow
  • 히트영역: 최소 44×44px (버튼, 필드, 체크박스 모두)
  • 모션 준수: prefers-reduced-motion 자동 처리
  • 색+기호 병행: 색만으로 상태 표시 금지 (성공=✓+색, 에러=⚠+색)

데모 & 개발

로컬에서 카탈로그 보기

pnpm demo

브라우저에서 http://localhost:5173 열기. 모든 컴포넌트 × 3스킨 × 상태(default/hover/active/disabled/error) 볼 수 있습니다.

개발·테스트

pnpm typecheck    # TypeScript 체크
pnpm lint         # ESLint
pnpm test         # Vitest
pnpm build        # ESM 번들 생성

TypeScript

모든 컴포넌트는 완전히 타입화되어 있습니다:

import { Button, ButtonProps } from '@ngsk2784-lab/ui';

const props: ButtonProps = {
  variant: 'primary',
  size: 'md',
  loading: false,
};

라이선스

MIT — 자유롭게 사용하세요.

지원

문제가 생기면: