1import { createSignal } from "solid-js";2import MinusIcon from "lucide-solid/icons/minus";3import PlusIcon from "lucide-solid/icons/plus";4
5import {6 NumberField,7 NumberFieldDecrementTrigger,8 NumberFieldDescription,9 NumberFieldErrorMessage,10 NumberFieldGroup,11 NumberFieldIncrementTrigger,12 NumberFieldInput,13 NumberFieldLabel,14} from "~/components/ui/number-field";15
16export default function NumberFieldDemo() {17 const [quantity, setQuantity] = createSignal(0);18
19 return (20 <div>21 <NumberField22 validationState={quantity() < 0 ? "invalid" : "valid"}23 value={quantity()}24 onChange={setQuantity}25 class="w-64"26 >27 <NumberFieldLabel>Quantity</NumberFieldLabel>28 <NumberFieldGroup>29 <NumberFieldInput />30 <NumberFieldIncrementTrigger>31 <PlusIcon class="size-3" />32 </NumberFieldIncrementTrigger>33 <NumberFieldDecrementTrigger>34 <MinusIcon class="size-3" />35 </NumberFieldDecrementTrigger>36 </NumberFieldGroup>37 <NumberFieldErrorMessage>38 Quantity must be greater or equal to 039 </NumberFieldErrorMessage>40 <NumberFieldDescription>41 Use the buttons to increase or decrease the quantity.42 </NumberFieldDescription>43 </NumberField>44 </div>45 );46}
npx shadcn@latest add https://solid-ui-neobrutalism.vercel.app/r/number-field.json
yarn shadcn@latest add https://solid-ui-neobrutalism.vercel.app/r/number-field.json
pnpm dlx shadcn@latest add https://solid-ui-neobrutalism.vercel.app/r/number-field.json
bunx --bun shadcn@latest add https://solid-ui-neobrutalism.vercel.app/r/number-field.json
Install the following dependencies
npm install @kobalte/core
yarn add @kobalte/core
pnpm add @kobalte/core
bun add @kobalte/core
Copy and paste the following code into your project
1import type { PolymorphicProps } from "@kobalte/core/polymorphic";2import type { Component, ComponentProps, JSX, ValidComponent } from "solid-js";3
4import { Show, splitProps } from "solid-js";5import * as NumberFieldPrimitive from "@kobalte/core/number-field";6import ChevronDownIcon from "lucide-solid/icons/chevron-down";7import ChevronUpIcon from "lucide-solid/icons/chevron-up";8
9import { cn } from "~/lib/utils";10
11const NumberField = <T extends ValidComponent = "div">(12 props: PolymorphicProps<T, NumberFieldPrimitive.NumberFieldRootProps<T>>,13) => {14 return (15 <NumberFieldPrimitive.Root data-slot="number-field" gutter={4} {...props} />16 );17};18
19const NumberFieldGroup: Component<ComponentProps<"div">> = (props) => {20 const [local, others] = splitProps(props, ["class"]);21 return (22 <div23 data-slot="number-field-group"24 class={cn(25 "relative rounded-md focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2",26 local.class,27 )}28 {...others}29 />30 );31};32
33type NumberFieldLabelProps<T extends ValidComponent = "label"> =34 NumberFieldPrimitive.NumberFieldLabelProps<T> & {35 class?: string | undefined;36 };37
38const NumberFieldLabel = <T extends ValidComponent = "label">(39 props: PolymorphicProps<T, NumberFieldLabelProps<T>>,40) => {41 const [local, others] = splitProps(props as NumberFieldLabelProps, ["class"]);42 return (43 <NumberFieldPrimitive.Label44 data-slot="number-field-label"45 class={cn(46 "text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70",47 local.class,48 )}49 {...others}50 />51 );52};53
54type NumberFieldInputProps<T extends ValidComponent = "input"> =55 NumberFieldPrimitive.NumberFieldInputProps<T> & {56 class?: string | undefined;57 };58
59const NumberFieldInput = <T extends ValidComponent = "input">(60 props: PolymorphicProps<T, NumberFieldInputProps<T>>,61) => {62 const [local, others] = splitProps(props as NumberFieldInputProps, ["class"]);63 return (64 <NumberFieldPrimitive.Input65 data-slot="number-field-input"66 class={cn(67 "flex h-10 w-full rounded-base border-2 border-border bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 ui-invalid:border-error-foreground ui-invalid:text-error-foreground",68 local.class,69 )}70 {...others}71 />72 );73};74
75type NumberFieldIncrementTriggerProps<T extends ValidComponent = "button"> =76 NumberFieldPrimitive.NumberFieldIncrementTriggerProps<T> & {77 class?: string | undefined;78 children?: JSX.Element;79 };80
81const NumberFieldIncrementTrigger = <T extends ValidComponent = "button">(82 props: PolymorphicProps<T, NumberFieldIncrementTriggerProps<T>>,83) => {84 const [local, others] = splitProps(85 props as NumberFieldIncrementTriggerProps,86 ["class", "children"],87 );88 return (89 <NumberFieldPrimitive.IncrementTrigger90 data-slot="number-field-increment-trigger"91 class={cn(92 "absolute top-1 right-1 inline-flex size-4 items-center justify-center",93 local.class,94 )}95 {...others}96 >97 <Show when={local.children} fallback={<ChevronUpIcon class="size-4" />}>98 {(children) => children()}99 </Show>100 </NumberFieldPrimitive.IncrementTrigger>101 );102};103
104type NumberFieldDecrementTriggerProps<T extends ValidComponent = "button"> =105 NumberFieldPrimitive.NumberFieldDecrementTriggerProps<T> & {106 class?: string | undefined;107 children?: JSX.Element;108 };109
110const NumberFieldDecrementTrigger = <T extends ValidComponent = "button">(111 props: PolymorphicProps<T, NumberFieldDecrementTriggerProps<T>>,112) => {113 const [local, others] = splitProps(114 props as NumberFieldDecrementTriggerProps,115 ["class", "children"],116 );117 return (118 <NumberFieldPrimitive.DecrementTrigger119 data-slot="number-field-decrement-trigger"120 class={cn(121 "absolute right-1 bottom-1 inline-flex size-4 items-center justify-center",122 local.class,123 )}124 {...others}125 >126 <Show when={local.children} fallback={<ChevronDownIcon class="size-4" />}>127 {(children) => children()}128 </Show>129 </NumberFieldPrimitive.DecrementTrigger>130 );131};132
133type NumberFieldDescriptionProps<T extends ValidComponent = "div"> =134 NumberFieldPrimitive.NumberFieldDescriptionProps<T> & {135 class?: string | undefined;136 };137
138const NumberFieldDescription = <T extends ValidComponent = "div">(139 props: PolymorphicProps<T, NumberFieldDescriptionProps<T>>,140) => {141 const [local, others] = splitProps(props as NumberFieldDescriptionProps, [142 "class",143 ]);144 return (145 <NumberFieldPrimitive.Description146 data-slot="number-field-description"147 class={cn("text-sm text-muted-foreground", local.class)}148 {...others}149 />150 );151};152
153type NumberFieldErrorMessageProps<T extends ValidComponent = "div"> =154 NumberFieldPrimitive.NumberFieldErrorMessageProps<T> & {155 class?: string | undefined;156 };157
158const NumberFieldErrorMessage = <T extends ValidComponent = "div">(159 props: PolymorphicProps<T, NumberFieldErrorMessageProps<T>>,160) => {161 const [local, others] = splitProps(props as NumberFieldErrorMessageProps, [162 "class",163 ]);164 return (165 <NumberFieldPrimitive.ErrorMessage166 data-slot="number-field-error-message"167 class={cn("text-sm text-error-foreground", local.class)}168 {...others}169 />170 );171};172
173export {174 NumberField,175 NumberFieldGroup,176 NumberFieldLabel,177 NumberFieldInput,178 NumberFieldIncrementTrigger,179 NumberFieldDecrementTrigger,180 NumberFieldDescription,181 NumberFieldErrorMessage,182};
Update the import paths to match your project setup.
1import {2 NumberField,3 NumberFieldDecrementTrigger,4 NumberFieldDescription,5 NumberFieldErrorMessage,6 NumberFieldGroup,7 NumberFieldIncrementTrigger,8 NumberFieldInput,9 NumberFieldLabel,10} from "~/components/ui/number-field";
1<NumberField>2 <NumberFieldLabel>Number Field</NumberFieldLabel>3 <NumberFieldGroup>4 <NumberFieldInput />5 <NumberFieldIncrementTrigger>+</NumberFieldIncrementTrigger>6 <NumberFieldDecrementTrigger>-</NumberFieldDecrementTrigger>7 </NumberFieldGroup>8</NumberFieldGroup>