All components
NucleusInput
InputsA text field that handles the boring parts for you — label wiring, helper text and a red error state with proper aria attributes.
Preview
We never share it.
That handle is taken.
Installation
- 1shadcn/ui initialized (`npx shadcn@latest init`)
- 2Tailwind CSS v4 configured with the nucleusUI theme tokens
- 3shadcn Input + Label (`npx shadcn@latest add input label`)
npm install lucide-react clsx tailwind-mergeAdd to your globals.css
The nucleusUI accent lives in these CSS variables — without them the component falls back to the default shadcn zinc theme.
app/globals.css
:root {
--primary: oklch(0.54 0.24 291);
--primary-foreground: oklch(0.985 0 0);
--ring: oklch(0.54 0.24 291);
--accent: oklch(0.96 0.03 291);
--accent-foreground: oklch(0.4 0.19 291);
}
.dark {
--primary: oklch(0.68 0.21 291);
--primary-foreground: oklch(0.145 0 0);
--ring: oklch(0.68 0.21 291);
--accent: oklch(0.26 0.06 291);
--accent-foreground: oklch(0.87 0.09 291);
}Usage
tsx
import { NucleusInput } from "@/components/nucleus/nucleus-input";
export function Example() {
return (
<div className="flex w-72 flex-col gap-4">
<NucleusInput placeholder="Default" />
<NucleusInput
label="Email"
type="email"
placeholder="you@example.com"
hint="We never share it."
/>
<NucleusInput
label="Workspace"
placeholder="acme-inc"
error="That handle is taken."
/>
</div>
);
}Source
Copy this file into components/nucleus/nucleus-input.tsx in your project — it's yours to own and modify.
components/nucleus/nucleus-input.tsx
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export interface NucleusInputProps extends React.ComponentProps<"input"> {
/** Optional label rendered above the field. */
label?: string;
/** Helper text shown below the field. */
hint?: string;
/** Error message — switches the field into its error state. */
error?: string;
}
export const NucleusInput = React.forwardRef<HTMLInputElement, NucleusInputProps>(
function NucleusInput({ className, label, hint, error, id, ...props }, ref) {
const autoId = React.useId();
const inputId = id ?? autoId;
const describedBy = error ? `${inputId}-error` : hint ? `${inputId}-hint` : undefined;
return (
<div className="flex w-full flex-col gap-1.5" data-slot="nucleus-input">
{label && <Label htmlFor={inputId}>{label}</Label>}
<Input
ref={ref}
id={inputId}
className={cn(
"h-10 rounded-xl px-3.5 transition-shadow",
error &&
"border-destructive aria-invalid:border-destructive focus-visible:ring-destructive/25",
className
)}
aria-invalid={error ? true : undefined}
aria-describedby={describedBy}
{...props}
/>
{error ? (
<p id={`${inputId}-error`} className="text-xs font-medium text-destructive">
{error}
</p>
) : hint ? (
<p id={`${inputId}-hint`} className="text-xs text-muted-foreground">
{hint}
</p>
) : null}
</div>
);
}
);
Props
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Optional label rendered above the field. |
hint | string | — | Helper text shown below the field. |
error | string | — | Error message — switches the field into its error state and sets aria-invalid. |
...rest | InputHTMLAttributes<HTMLInputElement> | — | All native input attributes are forwarded. |