neato
v0.0.21
Published
A powerful utility library for efficient CSS class management in React applications
Maintainers
Readme
neato
Language : English | 한국어
Tailwind CSS를 위한 완전한 스타일링 솔루션 - 클래스 관리부터 테마까지 모든 것을 하나로
✨ 특징
- 🎯 간단한 API - 하나의 함수로 모든 클래스 병합 요구사항 처리
- 🔧 스마트 병합 - Tailwind CSS 클래스 충돌 자동 해결
- 🎨 Variants 시스템 - 타입 안전성을 갖춘 강력한 조건부 스타일링
- 🧩 Compound Variants - 여러 조건에 기반한 복잡한 스타일링
- 📱 Multi-slot 지원 - 컴포넌트의 여러 부분을 독립적으로 스타일링
- 🌓 테마 시스템 - Light/Dark/System 테마를 지원하는 완전한 테마 관리
- 🎨 색상 유틸리티 - 다크모드 색상 변환 및 CSS 필터 생성 CLI 도구
- 🚀 TypeScript 우선 - 완전한 타입 안전성과 IntelliSense
- 📦 경량 - 런타임 오버헤드 없이 최소 번들 크기
- ⚡ 빠름 - 성능에 최적화
📦 설치
npm install neato
yarn add neato
pnpm add neato🎨 CLI 색상 유틸리티
neato는 색상 변환을 위한 강력한 CLI 도구를 포함하고 있습니다.
다크모드 색상 변환
라이트모드 색상을 다크모드에 적합한 색상으로 자동 변환:
npx neato toDark 3b82f6 ef4444 10b981
# 출력:
# #4d5fb8
# #b83c3c
# #0d8a5fCSS 필터 생성
원하는 색상을 구현하는 CSS 필터를 계산:
npx neato toFilter 3b82f6
# 출력:
# Input Color: #3b82f6
# Filter: filter: invert(32%) sepia(77%) saturate(2815%) hue-rotate(217deg) brightness(101%) contrast(101%);
# Loss: 0.89다크모드 + 필터 변환 파이프라인
색상을 다크모드로 변환한 후 CSS 필터까지 생성:
npx neato toDarkFilter 3b82f6
# 출력:
# Input Color: #3b82f6
# Light Filter: filter: invert(32%) sepia(77%) saturate(2815%) hue-rotate(217deg) brightness(101%) contrast(101%);
# Light Filter Loss: 0.89
# Dark Mode Color: #4d5fb8
# Dark Filter: filter: invert(36%) sepia(41%) saturate(1042%) hue-rotate(211deg) brightness(95%) contrast(96%);
# Dark Filter Loss: 1.23이러한 CLI 도구들은 디자인 시스템에서 일관된 색상 팔레트를 구축하고, 아이콘이나 SVG 요소에 동적 색상을 적용할 때 특히 유용합니다.
## 🛠️ Tailwind CSS IntelliSense 연동
Tailwind CSS 자동완성(IntelliSense)을 neato/neatoVariants 함수에서 사용하려면, 프로젝트 루트의 `.vscode/settings.json` 또는 VS Code의 전역 설정(`Ctrl/Cmd + ,` → `settings.json`)에 아래 설정을 추가하세요:
```jsonc
{
"tailwindCSS.experimental.classRegex": [
["neatoVariants\\(([^)]*)\\)", "[\"'`]([^\"'`]*)[\"'`]"] ,
["neato\\(([^)]*)\\)", "[\"'`]([^\"'`]*)[\"'`]"]
]
}- VS Code를 완전히 재시작해야 적용됩니다.
- Tailwind CSS IntelliSense 확장이 설치되어 있어야 합니다.
🚀 빠른 시작
기본 사용법
import { neato } from 'neato';
// 간단한 클래스 병합
const className = neato(
'px-4 py-2 rounded',
'bg-blue-500 text-white',
isActive && 'ring-2 ring-blue-300',
disabled && 'opacity-50 cursor-not-allowed'
);
// Tailwind 충돌 자동 해결
neato('px-2 px-4'); // → 'px-4' (나중 값이 우선)
neato('text-lg text-sm'); // → 'text-sm'고급 Variants 시스템
재사용 가능하고 타입 안전한 컴포넌트 스타일 생성:
import { neatoVariants } from 'neato';
const buttonStyles = neatoVariants({
base: 'inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none',
variants: {
variant: {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
outline: 'border border-gray-300 bg-transparent hover:bg-gray-50',
ghost: 'bg-transparent hover:bg-gray-100'
},
size: {
sm: 'h-8 px-3 text-sm',
md: 'h-10 px-4 text-base',
lg: 'h-12 px-6 text-lg'
}
},
compoundVariants: [
{
variant: 'outline',
size: 'lg',
className: 'border-2'
}
],
defaultVariants: {
variant: 'primary',
size: 'md'
}
});
// React에서 사용
function Button({ variant, size, className, ...props }) {
return (
<button
className={buttonStyles({ variant, size, className })}
{...props}
/>
);
}
// IntelliSense와 함께 완전한 타입 지원
<Button variant="secondary" size="lg" />🌓 테마 시스템
neato는 React 애플리케이션에서 쉽게 사용할 수 있는 완전한 테마 관리 시스템을 제공합니다.
기본 설정
import { ThemeProvider } from 'neato/theme';
import { createThemeScript } from 'neato/theme-script';
// 1. 앱 최상단에 Provider 설정
function App() {
return (
<ThemeProvider>
<YourComponents />
</ThemeProvider>
);
}
// 2. FOUC 방지를 위해 HTML head에 스크립트 추가 (Next.js 예시)
export default function RootLayout({ children }) {
return (
<html>
<head>
<script
dangerouslySetInnerHTML={{
__html: createThemeScript()
}}
/>
</head>
<body>
<ThemeProvider>
{children}
</ThemeProvider>
</body>
</html>
);
}Tailwind CSS 설정
테마 시스템과 함께 사용하려면 다음과 같이 설정하세요:
Tailwind CSS v4 사용시:
global.css 또는 메인 CSS 파일에 다음을 추가:
@custom-variant dark (&:where(.dark, .dark *));Tailwind CSS v3 사용시:
tailwind.config.js에 다음 설정을 추가:
module.exports = {
darkMode: ['class'],
content: [
'./src/**/*.{js,ts,jsx,tsx}',
// 다른 경로들...
],
theme: {
extend: {
// 커스텀 스타일 확장...
}
},
plugins: [
// 다른 플러그인들...
]
};테마 사용법
import { useTheme } from 'neato/theme';
function ThemeToggle() {
const { theme, setTheme, effectiveTheme, isHydrated } = useTheme();
return (
<div>
<button onClick={() => setTheme('light')}>
라이트 모드
</button>
<button onClick={() => setTheme('dark')}>
다크 모드
</button>
<button onClick={() => setTheme('system')}>
시스템 설정
</button>
<p>현재 테마: {theme}</p>
<p>적용된 테마: {effectiveTheme}</p>
</div>
);
}테마별 스타일링
// Tailwind CSS의 dark: modifier와 함께 사용
const cardStyles = neatoVariants({
base: 'p-6 rounded-lg border transition-colors',
variants: {
variant: {
default: 'bg-white border-gray-200 dark:bg-gray-800 dark:border-gray-700',
elevated: 'bg-white border-gray-200 shadow-lg dark:bg-gray-800 dark:border-gray-700'
}
}
});
function Card({ variant = 'default', children }) {
return (
<div className={cardStyles({ variant })}>
{children}
</div>
);
}고급 테마 토글 컴포넌트
import { useTheme } from 'neato/theme';
function AdvancedThemeToggle() {
const { theme, setTheme, effectiveTheme } = useTheme();
const cycleTheme = () => {
if (theme === 'light') setTheme('dark');
else if (theme === 'dark') setTheme('system');
else setTheme('light');
};
const getIcon = () => {
if (theme === 'system') return '🌓';
return effectiveTheme === 'dark' ? '🌙' : '☀️';
};
const getLabel = () => {
if (theme === 'system') return '시스템';
return theme === 'dark' ? '다크' : '라이트';
};
return (
<button
onClick={cycleTheme}
className="flex items-center gap-2 px-3 py-2 rounded-lg bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
>
<span>{getIcon()}</span>
<span>{getLabel()}</span>
</button>
);
}테마 API
useTheme()
테마 상태와 제어 함수를 반환합니다.
theme: 현재 설정된 테마 ('light' | 'dark' | 'system')- 사용자가 직접 설정한 테마 모드
'light'- 라이트 모드 선택'dark'- 다크 모드 선택'system'- 시스템 설정을 따르도록 선택
setTheme: 테마를 변경하는 함수- 타입:
(theme: NeatoTheme) => void - 사용법:
setTheme('dark'),setTheme('light'),setTheme('system')
- 타입:
effectiveTheme: 실제로 적용된 테마 ('light' | 'dark')- 실제로 적용되고 있는 테마 (DOM에 반영된 최종 테마)
theme이'system'이고 사용자 OS가 다크모드인 경우 →effectiveTheme은'dark'theme이'light'인 경우 →effectiveTheme은'light'
isHydrated: 클라이언트 하이드레이션 완료 여부- 타입:
boolean true- 브라우저에서 JavaScript가 실행되어 테마 기능 사용 가능false- 서버 렌더링 중이거나 아직 하이드레이션 전 (테마 변경 비활성화)
- 타입:
// 실제 사용 예시
function ThemeStatus() {
const { theme, setTheme, effectiveTheme, isHydrated } = useTheme();
if (!isHydrated) {
return <div>로딩 중...</div>; // 하이드레이션 전
}
return (
<div>
<p>설정된 테마: {theme}</p>
<p>실제 적용된 테마: {effectiveTheme}</p>
{/* 시스템 테마일 때는 실제 적용된 테마와 다를 수 있음 */}
{theme === 'system' && (
<p>시스템 설정을 따라 {effectiveTheme} 모드로 표시됩니다</p>
)}
</div>
);
}createThemeScript()
FOUC(Flash of Unstyled Content) 방지를 위한 인라인 스크립트 문자열을 생성합니다.
특징
- ✅ 자동 시스템 테마 감지 -
prefers-color-scheme미디어 쿼리 지원 - ✅ LocalStorage 연동 - 설정 자동 저장 및 복원
- ✅ SSR 안전 - 서버 사이드 렌더링과 하이드레이션 안전
- ✅ FOUC 방지 - 페이지 로드 시 깜빡임 없음
- ✅ TypeScript 지원 - 완전한 타입 안전성
📚 API 레퍼런스
neato(...inputs)
Tailwind 충돌 자동 해결과 함께 클래스를 병합합니다.
neato(
'base-classes',
condition && 'conditional-classes',
{ 'class-name': boolean },
['array', 'of', 'classes'],
undefined, // 무시됨
null // 무시됨
);neatoVariants(config)
컴포넌트 스타일링을 위한 타입 안전한 variant 시스템을 생성합니다.
단일 컴포넌트 모드
const styles = neatoVariants({
base: 'base-classes',
variants: {
variantName: {
option1: 'classes-for-option1',
option2: 'classes-for-option2'
}
},
compoundVariants: [
{
variantName: 'option1',
anotherVariant: 'value',
className: 'additional-classes'
}
],
defaultVariants: {
variantName: 'option1'
}
});
// 반환값: string
const className = styles({ variantName: 'option2' });Multi-slot 컴포넌트 모드
const cardStyles = neatoVariants({
container: {
base: 'rounded-lg border bg-white shadow-sm',
variants: {
size: { sm: 'p-4', md: 'p-6', lg: 'p-8' }
}
},
header: {
base: 'border-b pb-4 mb-4',
variants: {
align: { left: 'text-left', center: 'text-center', right: 'text-right' }
}
},
content: {
base: 'text-gray-700',
variants: {
spacing: { tight: 'space-y-2', normal: 'space-y-4', loose: 'space-y-6' }
}
}
});
// 각 슬롯별로 함수로 접근
cardStyles.container({ size: 'lg' }); // "rounded-lg border bg-white shadow-sm p-8"
cardStyles.header({ align: 'center', className: 'font-bold' }); // "border-b pb-4 mb-4 text-center font-bold"
cardStyles.content({ spacing: 'loose' }); // "text-gray-700 space-y-6"이제 멀티 슬롯 컴포넌트에서 각 부분별 스타일을 독립적으로 사용할 수 있습니다.
🎯 실제 사용 예시
채팅 메시지 컴포넌트
const messageStyles = neatoVariants({
base: 'max-w-xs lg:max-w-md px-4 py-2 rounded-lg break-words',
variants: {
sender: {
user: 'bg-blue-500 text-white ml-auto',
other: 'bg-gray-200 text-gray-900 mr-auto'
},
status: {
sending: 'opacity-70',
sent: 'opacity-100',
failed: 'opacity-50 border border-red-300'
}
},
compoundVariants: [
{
sender: 'other',
status: 'sent',
className: 'shadow-sm'
}
]
});
function ChatMessage({ content, sender, status }) {
return (
<div className={messageStyles({ sender, status })}>
{content}
</div>
);
}여러 부분으로 구성된 카드 컴포넌트
const cardStyles = neatoVariants({
container: {
base: 'rounded-lg border bg-white shadow-sm overflow-hidden',
variants: {
size: {
sm: 'p-4',
md: 'p-6',
lg: 'p-8'
}
}
},
header: {
base: 'border-b pb-4 mb-4',
variants: {
align: {
left: 'text-left',
center: 'text-center',
right: 'text-right'
}
}
},
content: {
base: 'text-gray-700',
variants: {
spacing: {
tight: 'space-y-2',
normal: 'space-y-4',
loose: 'space-y-6'
}
}
}
});
function Card({ size, headerAlign, contentSpacing, title, children }) {
return (
<div className={cardStyles.container({ size })}>
<header className={cardStyles.header({ align: headerAlign })}>
<h3>{title}</h3>
</header>
<div className={cardStyles.content({ spacing: contentSpacing })}>
{children}
</div>
</div>
);
}🤝 왜 neato인가?
neato 사용 전
// 장황하고 오류가 발생하기 쉬움
<div className={clsx(
'animate-slide-up-fade max-w-md rounded-md px-4 py-2 shadow',
isMine
? 'bg-blue-100 ml-auto justify-end'
: 'mr-auto justify-start',
!isMine && isConnected && 'ml-12',
hasError && 'border border-red-300',
className
)} />neato 사용 후
// 깔끔하고 유지보수하기 쉬움
const messageStyles = neatoVariants({
base: 'animate-slide-up-fade max-w-md rounded-md px-4 py-2 shadow',
variants: {
owner: {
mine: 'bg-blue-100 ml-auto justify-end',
other: 'mr-auto justify-start'
},
connected: { true: '', false: '' },
error: { true: 'border border-red-300', false: '' }
},
compoundVariants: [
{
owner: 'other',
connected: true,
className: 'ml-12'
}
]
});
<div className={messageStyles({
owner: isMine ? 'mine' : 'other',
connected: isConnected,
error: hasError,
className
})} />📖 TypeScript 지원
neato는 TypeScript로 구축되어 우수한 타입 안전성을 제공합니다:
// 완전한 타입 지원 variants
const styles = neatoVariants({
variants: {
size: {
sm: '...',
md: '...',
lg: '...'
}
}
});
// TypeScript가 유효한 옵션을 강제합니다
styles({ size: 'xl' }); // ❌ 오류: 'xl'은 할당할 수 없습니다
styles({ size: 'lg' }); // ✅ 유효함🔧 설정
neato는 모든 Tailwind CSS 설정과 잘 작동합니다. 최적의 성능을 위해 tailwind.config.js에 neato를 사용하는 모든 파일이 포함되어 있는지 확인하세요:
module.exports = {
content: [
'./src/**/*.{js,ts,jsx,tsx}',
// neato를 사용하는 다른 경로들도 추가
],
// ... 나머지 설정
};📄 라이선스
MIT © Jeong Jinho
