Skip to content

shadcn/ui

shadcn/ui provides styled, accessible components built on Radix UI. Since raqam is headless, it integrates cleanly — just use raqam’s hooks/components and apply shadcn’s class names.

Create a reusable RaqamInput component that follows the shadcn design language:

components/raqam-input.tsx
"use client";
import * as React from "react";
import { NumberField } from "raqam";
import { cn } from "@/lib/utils";
import type { UseNumberFieldStateOptions } from "raqam";
interface RaqamInputProps extends UseNumberFieldStateOptions {
label?: string;
description?: string;
className?: string;
}
export function RaqamInput({
label,
description,
className,
...props
}: RaqamInputProps) {
return (
<NumberField.Root
{...props}
className={cn("flex flex-col gap-2", className)}
>
{label && (
<NumberField.Label className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
{label}
</NumberField.Label>
)}
<NumberField.Group className="flex h-9 rounded-md border border-input bg-transparent shadow-sm overflow-hidden group-data-[focused]:ring-1 group-data-[focused]:ring-ring group-data-[invalid]:border-destructive">
<NumberField.Decrement className="px-3 text-muted-foreground hover:text-foreground disabled:opacity-50 border-r border-input bg-muted/30 hover:bg-muted/50 transition-colors select-none text-sm">
</NumberField.Decrement>
<NumberField.Input className="flex-1 px-3 py-1 text-sm outline-none bg-transparent placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50" />
<NumberField.Increment className="px-3 text-muted-foreground hover:text-foreground disabled:opacity-50 border-l border-input bg-muted/30 hover:bg-muted/50 transition-colors select-none text-sm">
+
</NumberField.Increment>
</NumberField.Group>
{description && (
<NumberField.Description className="text-xs text-muted-foreground">
{description}
</NumberField.Description>
)}
<NumberField.ErrorMessage className="text-xs text-destructive" />
</NumberField.Root>
);
}
import { RaqamInput } from "@/components/raqam-input";
// Basic
<RaqamInput
locale="en-US"
label="Quantity"
defaultValue={1}
minValue={1}
step={1}
/>
// Currency
<RaqamInput
locale="en-US"
label="Price"
description="Enter the listing price in USD"
formatOptions={{ style: "currency", currency: "USD" }}
defaultValue={0}
minValue={0}
validate={(v) => v !== null && v > 0 ? true : "Price must be greater than $0"}
/>
import { Badge } from "@/components/ui/badge";
import { useNumberFieldFormat } from "raqam";
function PriceBadge({ amount }: { amount: number }) {
const formatted = useNumberFieldFormat(amount, {
locale: "en-US",
formatOptions: { style: "currency", currency: "USD" },
});
return (
<Badge variant="secondary" className="font-mono">
{formatted}
</Badge>
);
}
import { useForm } from "react-hook-form";
import { Form, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { NumberField } from "raqam";
import { Controller } from "react-hook-form";
export function ProductForm() {
const form = useForm({ defaultValues: { price: null as number | null } });
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(console.log)}>
<FormField
control={form.control}
name="price"
rules={{ required: "Price is required" }}
render={({ field }) => (
<FormItem>
<FormLabel>Price</FormLabel>
<NumberField.Root
locale="en-US"
formatOptions={{ style: "currency", currency: "USD" }}
value={field.value}
onChange={field.onChange}
onBlur={field.onBlur}
className="w-full"
>
<NumberField.Group className="flex h-9 rounded-md border border-input overflow-hidden focus-within:ring-1 focus-within:ring-ring">
<NumberField.Decrement className="px-3 bg-muted/30 border-r border-input text-sm text-muted-foreground hover:bg-muted/50">
</NumberField.Decrement>
<NumberField.Input className="flex-1 px-3 text-sm outline-none bg-transparent" />
<NumberField.Increment className="px-3 bg-muted/30 border-l border-input text-sm text-muted-foreground hover:bg-muted/50">
+
</NumberField.Increment>
</NumberField.Group>
</NumberField.Root>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
);
}