
Company News
Jerod Santo Joins Socket as Head of Media
Allow myself to introduce... myself.
Language : English | 한국어
Tailwind CSS를 위한 완전한 스타일링 솔루션 - 클래스 관리부터 테마까지 모든 것을 하나로
npm install neato
yarn add neato
pnpm add neato
Tailwind CSS 자동완성(IntelliSense)을 neato/neatoVariants 함수에서 사용하려면, 프로젝트 루트의 .vscode/settings.json 또는 VS Code의 전역 설정(Ctrl/Cmd + , → settings.json)에 아래 설정을 추가하세요:
{
"tailwindCSS.experimental.classRegex": [
["neatoVariants\\(([^)]*)\\)", "[\"'`]([^\"'`]*)[\"'`]"] ,
["neato\\(([^)]*)\\)", "[\"'`]([^\"'`]*)[\"'`]"]
]
}
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'
재사용 가능하고 타입 안전한 컴포넌트 스타일 생성:
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 { NeatoThemeProvider } from 'neato/theme';
import { createNeatoThemeScript } from 'neato/theme-script';
// 1. 앱 최상단에 Provider 설정
function App() {
return (
<NeatoThemeProvider>
<YourComponents />
</NeatoThemeProvider>
);
}
// 2. FOUC 방지를 위해 HTML head에 스크립트 추가 (Next.js 예시)
export default function RootLayout({ children }) {
return (
<html>
<head>
<script
dangerouslySetInnerHTML={{
__html: createNeatoThemeScript()
}}
/>
</head>
<body>
<NeatoThemeProvider>
{children}
</NeatoThemeProvider>
</body>
</html>
);
}
테마 시스템과 함께 사용하려면 다음과 같이 설정하세요:
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 { useNeatoTheme } from 'neato/theme';
function ThemeToggle() {
const { theme, setTheme, effectiveTheme, isHydrated } = useNeatoTheme();
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 { useNeatoTheme } from 'neato/theme';
function AdvancedThemeToggle() {
const { theme, setTheme, effectiveTheme } = useNeatoTheme();
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>
);
}
useNeatoTheme()테마 상태와 제어 함수를 반환합니다.
theme: 현재 설정된 테마 ('light' | 'dark' | 'system')
'light' - 라이트 모드 선택'dark' - 다크 모드 선택'system' - 시스템 설정을 따르도록 선택setTheme: 테마를 변경하는 함수
(theme: NeatoTheme) => voidsetTheme('dark'), setTheme('light'), setTheme('system')effectiveTheme: 실제로 적용된 테마 ('light' | 'dark')
theme이 'system'이고 사용자 OS가 다크모드인 경우 → effectiveTheme은 'dark'theme이 'light'인 경우 → effectiveTheme은 'light'isHydrated: 클라이언트 하이드레이션 완료 여부
booleantrue - 브라우저에서 JavaScript가 실행되어 테마 기능 사용 가능false - 서버 렌더링 중이거나 아직 하이드레이션 전 (테마 변경 비활성화)// 실제 사용 예시
function ThemeStatus() {
const { theme, setTheme, effectiveTheme, isHydrated } = useNeatoTheme();
if (!isHydrated) {
return <div>로딩 중...</div>; // 하이드레이션 전
}
return (
<div>
<p>설정된 테마: {theme}</p>
<p>실제 적용된 테마: {effectiveTheme}</p>
{/* 시스템 테마일 때는 실제 적용된 테마와 다를 수 있음 */}
{theme === 'system' && (
<p>시스템 설정을 따라 {effectiveTheme} 모드로 표시됩니다</p>
)}
</div>
);
}
createNeatoThemeScript()FOUC(Flash of Unstyled Content) 방지를 위한 인라인 스크립트 문자열을 생성합니다.
prefers-color-scheme 미디어 쿼리 지원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' });
const multi = neatoVariants({
icon: {
base: 'w-4 h-4',
variants: { color: { red: 'text-red-500', blue: 'text-blue-500' } }
},
label: {
base: 'font-bold',
variants: { size: { sm: 'text-sm', lg: 'text-lg' } }
}
});
// 각 슬롯별로 함수로 접근
multi.icon({ color: 'red' }); // "w-4 h-4 text-red-500"
multi.label({ size: 'lg', className: 'underline' }); // "font-bold text-lg underline"
이제 멀티 슬롯 컴포넌트에서 각 부분별 스타일을 독립적으로 사용할 수 있습니다.
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 }) {
const styles = cardStyles({
container: { size },
header: { align: headerAlign },
content: { spacing: contentSpacing }
});
return (
<div className={styles.container}>
<header className={styles.header}>
<h3>{title}</h3>
</header>
<div className={styles.content}>
{children}
</div>
</div>
);
}
// 장황하고 오류가 발생하기 쉬움
<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
)} />
// 깔끔하고 유지보수하기 쉬움
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
})} />
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
FAQs
Deprecated. Use tailwind-variants and next-themes: https://ilokesto.ayden94.com/ko/migrate/neato
The npm package neato receives a total of 24 weekly downloads. As such, neato popularity was classified as not popular.
We found that neato demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Company News
Allow myself to introduce... myself.

Research
/Security News
A Twitch browser extension on Chrome and Firefox forwards users’ live OAuth session tokens through proxies controlled by a Russian bot service.

Security News
Anthropic found biased reasoning and recklessness drove Claude Mythos 5 to publish malware on PyPI and compromise a security vendor.