Component
Input With Success
Input with associated success confirmation copy.
- Component
- React
- TSX
- Tailwind CSS
- MIT
What it does
Capabilities
- Semantic HTML
- ARIA
- Associated labels
Preview
Live component
9:41 100%
devsnips.site/library/react/components/inputs/input-with-success/ fluid · no fixed width 100%
Install
Add to your project
npx devsnips add React/Components/Inputs/input-with-success React/Components/Inputs/input-with-success import type { ChangeEvent, InputHTMLAttributes, ReactNode } from "react";
import { useId, useState } from "react";
export type InputSize = "sm" | "md" | "lg";
export type InputTone = "default" | "error" | "success";
function cx(...parts: Array<string | false | null | undefined>): string {
return parts.filter(Boolean).join(" ");
}
const INPUT_SIZES: Record<InputSize, string> = {
sm: "h-8 px-2.5 text-[13px]",
md: "h-9 px-3 text-sm",
lg: "h-11 px-3.5 text-sm",
};
const ICON_SIZES: Record<InputSize, string> = {
sm: "[&_svg]:size-[14px]",
md: "[&_svg]:size-4",
lg: "[&_svg]:size-[18px]",
};
function inputClasses(size: InputSize, tone: InputTone, hasLeading = false, hasTrailing = false): string {
return cx(
"w-full rounded-[var(--ds-radius-sm)] border bg-[var(--ds-color-input)] text-[var(--ds-color-foreground)] shadow-none transition-colors duration-150 ease-out placeholder:text-[var(--ds-color-muted-foreground)] hover:bg-[var(--ds-color-input-hover,var(--ds-color-input))] focus:bg-[var(--ds-color-input-focus,var(--ds-color-input))] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ds-color-focus-ring)] disabled:pointer-events-none disabled:bg-[var(--ds-color-muted)] disabled:text-[var(--ds-color-muted-foreground)] disabled:opacity-60 read-only:bg-[var(--ds-color-surface-subtle)] read-only:text-[var(--ds-color-muted-foreground)] motion-reduce:transition-none",
tone === "error" ? "border-[var(--ds-color-destructive)]" : tone === "success" ? "border-[var(--ds-color-success)]" : "border-[var(--ds-color-border)] focus:border-[var(--ds-color-border-strong)]",
INPUT_SIZES[size],
hasLeading && "pl-9",
hasTrailing && "pr-9",
);
}
function Spinner() {
return (
<svg className="size-4 animate-spin motion-reduce:animate-none" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="3" className="opacity-25" />
<path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" strokeWidth="3" strokeLinecap="round" />
</svg>
);
}
export interface InputWithSuccessProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "prefix"> {
label?: string;
helperText?: string;
error?: string;
success?: string;
size?: InputSize;
tone?: InputTone;
leadingIcon?: ReactNode;
trailingIcon?: ReactNode;
prefix?: ReactNode;
suffix?: ReactNode;
}
export function InputWithSuccess({
label = "Input with Success",
helperText,
error,
success,
size = "md",
tone = "default",
leadingIcon,
trailingIcon,
prefix,
suffix,
id,
className,
type = "text",
disabled,
readOnly,
...props
}: InputWithSuccessProps) {
const generatedId = useId();
const inputId = id ?? `input-${generatedId}`;
const messageId = `${inputId}-message`;
const message = error ?? success ?? helperText;
const resolvedTone: InputTone = error ? "error" : success ? "success" : tone;
const hasLeading = Boolean(leadingIcon ?? prefix);
const hasTrailing = Boolean(trailingIcon ?? suffix);
return (
<div className="w-full">
<label htmlFor={inputId} className="mb-2 block text-[13px] font-medium leading-5 text-[var(--ds-color-foreground)]">
{label}
</label>
<div className="relative flex items-center">
{hasLeading ? (
<span className={cx("pointer-events-none absolute left-3 text-[var(--ds-color-muted-foreground)]", ICON_SIZES[size])}>
{prefix ?? leadingIcon}
</span>
) : null}
<input
id={inputId}
type={type}
className={cx(inputClasses(size, resolvedTone, hasLeading, hasTrailing), className)}
disabled={disabled}
readOnly={readOnly}
aria-invalid={error ? true : undefined}
aria-describedby={message ? messageId : undefined}
{...props}
/>
{hasTrailing ? (
<span className={cx("pointer-events-none absolute right-3 text-[var(--ds-color-muted-foreground)]", ICON_SIZES[size])}>
{suffix ?? trailingIcon}
</span>
) : null}
</div>
{message ? (
<p id={messageId} className={cx("mt-2 text-xs", error ? "text-[var(--ds-color-destructive)]" : success ? "text-[var(--ds-color-success)]" : "text-[var(--ds-color-muted-foreground)]")}>
{error ? `Error: ${error}` : success ? `Success: ${success}` : helperText}
</p>
) : null}
</div>
);
}
export default InputWithSuccess; /* DevSnips React — JavaScript parity build. Same API and behavior as code.tsx; TypeScript types removed. */
import { useId, useState } from "react";
function cx(...parts) {
return parts.filter(Boolean).join(" ");
}
const INPUT_SIZES = {
sm: "h-8 px-2.5 text-[13px]",
md: "h-9 px-3 text-sm",
lg: "h-11 px-3.5 text-sm",
};
const ICON_SIZES = {
sm: "[&_svg]:size-[14px]",
md: "[&_svg]:size-4",
lg: "[&_svg]:size-[18px]",
};
function inputClasses(size, tone, hasLeading = false, hasTrailing = false) {
return cx(
"w-full rounded-[var(--ds-radius-sm)] border bg-[var(--ds-color-input)] text-[var(--ds-color-foreground)] shadow-none transition-colors duration-150 ease-out placeholder:text-[var(--ds-color-muted-foreground)] hover:bg-[var(--ds-color-input-hover,var(--ds-color-input))] focus:bg-[var(--ds-color-input-focus,var(--ds-color-input))] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ds-color-focus-ring)] disabled:pointer-events-none disabled:bg-[var(--ds-color-muted)] disabled:text-[var(--ds-color-muted-foreground)] disabled:opacity-60 read-only:bg-[var(--ds-color-surface-subtle)] read-only:text-[var(--ds-color-muted-foreground)] motion-reduce:transition-none",
tone === "error" ? "border-[var(--ds-color-destructive)]" : tone === "success" ? "border-[var(--ds-color-success)]" : "border-[var(--ds-color-border)] focus:border-[var(--ds-color-border-strong)]",
INPUT_SIZES[size],
hasLeading && "pl-9",
hasTrailing && "pr-9",
);
}
function Spinner() {
return (
<svg className="size-4 animate-spin motion-reduce:animate-none" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="3" className="opacity-25" />
<path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" strokeWidth="3" strokeLinecap="round" />
</svg>
);
}
export function InputWithSuccess({
label = "Input with Success",
helperText,
error,
success,
size = "md",
tone = "default",
leadingIcon,
trailingIcon,
prefix,
suffix,
id,
className,
type = "text",
disabled,
readOnly,
...props
}) {
const generatedId = useId();
const inputId = id ?? `input-${generatedId}`;
const messageId = `${inputId}-message`;
const message = error ?? success ?? helperText;
const resolvedTone = error ? "error" : success ? "success" : tone;
const hasLeading = Boolean(leadingIcon ?? prefix);
const hasTrailing = Boolean(trailingIcon ?? suffix);
return (
<div className="w-full">
<label htmlFor={inputId} className="mb-2 block text-[13px] font-medium leading-5 text-[var(--ds-color-foreground)]">
{label}
</label>
<div className="relative flex items-center">
{hasLeading ? (
<span className={cx("pointer-events-none absolute left-3 text-[var(--ds-color-muted-foreground)]", ICON_SIZES[size])}>
{prefix ?? leadingIcon}
</span>
) : null}
<input
id={inputId}
type={type}
className={cx(inputClasses(size, resolvedTone, hasLeading, hasTrailing), className)}
disabled={disabled}
readOnly={readOnly}
aria-invalid={error ? true : undefined}
aria-describedby={message ? messageId : undefined}
{...props}
/>
{hasTrailing ? (
<span className={cx("pointer-events-none absolute right-3 text-[var(--ds-color-muted-foreground)]", ICON_SIZES[size])}>
{suffix ?? trailingIcon}
</span>
) : null}
</div>
{message ? (
<p id={messageId} className={cx("mt-2 text-xs", error ? "text-[var(--ds-color-destructive)]" : success ? "text-[var(--ds-color-success)]" : "text-[var(--ds-color-muted-foreground)]")}>
{error ? `Error: ${error}` : success ? `Success: ${success}` : helperText}
</p>
) : null}
</div>
);
}
export default InputWithSuccess; # Input With Success
Input with associated success confirmation copy.
## Usage
```tsx
import { InputWithSuccess } from './code';
<InputWithSuccess label="Workspace name" placeholder="Acme design" />
```
## JavaScript
```jsx
import { InputWithSuccess } from './code';
<InputWithSuccess label="Billing email" placeholder="[email protected]" />
```
## Props
| Name | Type | Default | Description |
|---|---|---:|---|
| `label` | `string` | component label | Visible label associated with the input. |
| `helperText` | `string` | `undefined` | Supporting text announced through `aria-describedby`. |
| `error` | `string` | `undefined` | Error message; sets `aria-invalid`. |
| `success` | `string` | `undefined` | Success message associated with the control. |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Control height and text sizing. |
| `tone` | `"default" \| "error" \| "success"` | `"default"` | Visual state when no message prop overrides it. |
| `className` | `string` | `undefined` | Adds layout classes to the input element. |
| native input props | `InputHTMLAttributes<HTMLInputElement>` | — | Supports `value`, `defaultValue`, `onChange`, `name`, `id`, `placeholder`, `disabled`, `readOnly`, `required`, and `aria-*`. |
## States
Supports default, hover, focus-visible, disabled, readonly, error, success, and loading where applicable.
## Accessibility
Uses a native `input`, a visible `label`, associated helper/error text, `aria-invalid` for errors, and keyboard-accessible buttons for interactive controls.
## Styling
Built with React, Tailwind CSS, and DevSnips design tokens. No component-specific CSS file is required.
## Design Tokens
Visual decisions reference `React/DESIGN_TOKENS.md` rather than duplicating the token system. 117 lines UTF-8 · LF · Spaces: 2
DevSnips AI ready Claude · Cursor · Windsurf
Agent skill & Model Context Protocol
Pass this component straight into your AI workflow. DevSnips exposes each resource as a structured, addressable registry entry, so your agent installs the real files instead of approximating them.
devsnips://components/inputs/input-with-success I want to use this DevSnips resource in production: Page: https://devsnips.site/library/react/components/inputs/input-with-success/ Install: npx devsnips add React/Components/Inputs/input-with-success This is the Input With Success — Input with associated success confirmation copy. Use this DevSnips resource as the starting point for my project. Adapt it to my project's stack and conventions, fix any issues, and make it production-ready while preserving its intended design and behavior.
Paste into ChatGPT, Claude or your agent — it already carries the page URL and the exact install path.
Resource URI
devsnips://components/inputs/input-with-success