A minimalist, intent-driven UI system where components are laws, not presets. You copy the code. You own it. You change it. The system guides you instead of trapping you.
Signal Layers exists because modern frontend UI has become over-configured, over-abstracted, and under-owned. This library fixes that through a revolutionary signal-based architecture where props are signals of intention, not configuration.
Required Dependencies:
react@>=16.8.0
react-dom@>=16.8.0
tailwindcss@v4npx signal-layers copyThis copies all components to src/components/signals/ in your project.
npx signal-layers add button
npx signal-layers add card
npx signal-layers add dropdownnpx signal-layers listSignal Layers introduces a revolutionary four-signal architecture:
-
Input Signals (
inputSignal)- Raw props passed to components
- User intention for styling
- Example:
primary,lg,onClick,disabled
-
Layer Signals (
layerSignal)- Mapped CSS classes and styling layers
- Visual presentation logic
- Example: Maps
primaryβ"bg-blue-500 text-white"
-
Data Signals (
dataSignal)- Processed and validated data
- Component state and computed values
- Example: Validated form values, computed dimensions
-
State Signals (
stateSignal)- React hooks for interactivity
- Hover, focus, ripple states
- Example:
stateSignal.hover.get,stateSignal.ripples.set
- One signal per layer: If multiple signals from the same layer are passed, the last one is applied
- Deterministic: No magic, no warnings, predictable behavior
- Composable: Signals can be combined freely
- Optional: All signals are opt-in
| Component | Description |
|---|---|
| Button | Interactive button with ripple effects |
| Card | Content container with layered styling |
| CheckBox | Custom checkbox with animations |
| Dropdown | Select dropdown with keyboard navigation |
| FabMenu | Floating action button menu |
| ProgressBar | Animated progress indicator |
| Slider | Range slider with drag interaction |
| Spinner | Loading spinner animations |
| Switch | Toggle switch with smooth transitions |
| TextField | Input field with validation states |
import { Button } from './components/signals/Button.jsx';
// Simple usage
<Button onClick={() => console.log('clicked')}>Click me</Button>
// With signals
<Button primary lg hoverEnlarge>Primary Action</Button>
// Multiple signals
<Button ghost red sm hoverLift activeShrink>Ghost Button</Button>import { FabMenu } from './components/signals/FabMenu.jsx';
<FabMenu
icon="+"
actions={[
{ icon: "π", label: "New Document", onClick: () => {} },
{ icon: "π", label: "Open File", onClick: () => {} },
{ icon: "πΎ", label: "Save", onClick: () => {} }
]}
/>import { TextField, CheckBox, Switch } from './components/signals';
<TextField placeholder="Enter your name" lg />
<CheckBox checked={isChecked} onChange={setIsChecked} />
<Switch on={isEnabled} onChange={setIsEnabled} />Define what the component is:
Tone/Style: primary, secondary, cta, ghost, neumorphism
Color: red, green, blue, yellow, purple
Size: xs, sm, md, lg, xl
Shape: square, rounded, pill, circle
Define how it behaves:
Hover: hoverEnlarge, hoverShrink, hoverLift, hoverFade
Active: activeShrink, activeRipple, activeExplode
Effects: ripple, innerShadow, noShadow
Display: block, inline, center
Border: border, noBorder, roundedBorder
Each component contains a signal mapping section where you can customize how signals translate to CSS:
// Inside Button.jsx
const layer = (name, scope = "btn") => (className) =>
(layerSignal[scope] ||= {},
layerSignal[scope][name] ||= [],
(layerSignal[scope][name][0] = className));
// Composite Signal
inputSignal.ghost && (
btn.shape("rounded-full"),
btn.shadow("shadow-none"),
btn.color("bg-transparent text-gray-800"),
btn.hover("hover:scale-110 hover:bg-gray-100/50 hover:text-gray-900"),
btn.active("active:scale-95 active:bg-gray-100/75 active:text-gray-900"),
btn.animation("transition-all duration-500 cursor-pointer")
)
// Individual Signals
inputSignal.xs && btn.size("px-2 py-1 text-xs"),
inputSignal.sm && btn.size("px-3 py-1.5 text-sm"),
inputSignal.md && btn.size("px-4 py-2 text-base"),
inputSignal.lg && btn.size("px-6 py-3 text-lg"),
inputSignal.xl && btn.size("px-8 py-4 text-xl")Interactive components use state signals for React hooks:
const state = (name, priority = 0, initial = false) => (
(stateSignal._hooks ||= {})[name] ||= (() => {
const [get, set] = useState(initial);
return { get, set };
})(),
priority && (!stateSignal._priority || priority > stateSignal._priority) &&
(stateSignal[name] = stateSignal._hooks[name], stateSignal._priority = priority)
);Each component follows this structure:
export function ComponentName(contract = {}) {
/* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
* CONTRACT
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
const [inputSignal, layerSignal, dataSignal, stateSignal] = [{ ...contract }, {}, {}, {}];
/* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
* CONTRACT TOOLS
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
const layer = (name, scope = "root") => (className) => { /* ... */ };
const data = (name, key = name) => { /* ... */ };
const state = (name, priority = 0, initial = false) => { /* ... */ };
/* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
* LAYERS
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
// Signal mappings here
/* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
* RENDER
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
return (
// JSX with className={classes(layerSignal.root)}
);
}- Copy, don't import: Components are copied into your project
- No vendor lock-in: You own the code completely
- No hidden logic: Everything is visible and editable
- The file is the product: Components are self-contained documentation
- No variant enums: Use
primary, notvariant="primary" - No size schemas: Use
lg, notsize="large" - Signals express intent: Props describe what you want, not how to configure it
- Composable API: Mix and match signals freely
- Respect browser behavior: Use
:hover,:focus,:active - Semantic HTML: Proper elements and accessibility
- Keyboard navigation: Built-in browser support
- No re-modeling: Don't fight the platform
Signal Layers follows the "copy and edit" philosophy. To contribute:
- Copy components to your project
- Make changes as needed
- Share improvements with the community
- No PRs required - this is a copy-first system
MIT License - feel free to use, modify, and distribute.
- GitHub: https://github.com/aryanbatras/minimalist-ui
- Issues: https://github.com/aryanbatras/minimalist-ui/issues
- NPM: https://www.npmjs.com/package/signal-layers
To get autocompletion and hover information for the signals, add the following to your vscode settings.json :
"tailwindCSS.experimental.classRegex": [
["\b\w+\s*\(\s*["']([^\"']*)["'`]"]
]