CronInput
A cron expression field that describes itself in English and previews the next few runs, so a five-field string stops being a guess. minInterval catches the * * * * * that somebody always ships by accident. An expression that is already equivalent is never rewritten — 1-5 stays 1-5 rather than being expanded to 1,2,3,4,5 the moment the field mounts.
$ npx shadcn@latest add https://input-cn.vercel.app/r/cron-input.jsonHand this to Claude Code
Most people reach this component through an agent rather than by reading the page. Pick how you want it wired and take a prompt that already carries the canonical value, the real prop names and the mistakes worth avoiding.
Nothing runs on its own. The link opens the tool with the prompt typed into the box; you read it and press Enter. If the button does nothing, Claude Code is not installed on this machine — use Copy prompt instead.
Live demo
At 09:00, Monday through Friday
Try */15 * * * * or @daily.
value = "0 9 * * 1-5"
Constraints
Validation rules, declared as props. Each one produces its own message and its own data-rule value, so a test can assert the rule rather than the sentence.
Props
Shared props
Implemented identically by all eleven components. These tables are generated from the source interfaces at build time, so a renamed prop changes this page rather than quietly making it wrong.
With react-hook-form
import { useForm, Controller } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { cronSchema } from "@inputcn/cron/schema"
import { CronInput } from "@/components/ui/cron-input"
import { z } from "zod"
// The companion schema validates the CANONICAL value, so the resolver and the
// field can never disagree about what "valid" means.
const schema = z.object({ schedule: cronSchema() })
export function Example() {
const { control } = useForm({ resolver: zodResolver(schema) })
return (
<Controller
name="schedule"
control={control}
render={({ field, fieldState }) => (
<CronInput
label="Schedule"
{...field}
aria-invalid={fieldState.invalid}
/>
)}
/>
)
}With a Server Action
import { CronInput } from "@/components/ui/cron-input"
export default function Page() {
return (
<form action={save}>
<CronInput name="schedule" label="Schedule" />
<button>Save</button>
</form>
)
}
async function save(data: FormData) {
"use server"
// The hidden canonical input carries the real value, not the display text.
const schedule = data.get("schedule") // "0 9 * * 1-5"
}Keyboard
Emits string, for example "0 9 * * 1-5". Keyboard operation is covered by automated tests; screen readers have not been verified yet.