{
if (!error) return null;
return (
{error.message}
);
}}
/>
```
## Usage Patterns
### With RHFInputGroup
The easiest way to use RHFError is through [RHFInputGroup](/docs/rhf-input-group), which automatically includes it:
```tsx
```
### Manual Layout
For custom layouts, use RHFError manually:
```tsx
Email Address
```
### Multiple Errors
For fields with multiple validation rules, Zod returns the first error encountered:
```tsx
const schema = z.object({
password: z.string()
.min(8, 'Minimum 8 characters')
.regex(/[A-Z]/, 'Must contain uppercase')
.regex(/[0-9]/, 'Must contain number')
});
```
## Best Practices
1. **Validation mode** - Use `mode: 'onBlur'` for better UX:
```tsx
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur' // Shows errors after user leaves field
});
```
2. **Error messages** - Write clear, actionable error messages:
```tsx
z.string().min(8, 'Password must be at least 8 characters')
// â
Clear and actionable
z.string().min(8, 'Invalid')
// â Too vague
```
3. **Consistent styling** - Use `className` for simple styling, `render` for complex designs:
```tsx
```
4. **Accessibility** - Error messages are automatically associated with inputs when using RHFInputGroup
## Accessibility
* Error messages are rendered in a `` tag with proper ARIA attributes via the `InputError` component
* Errors are announced to screen readers when they appear
* The `GeckoUIRHFError` CSS class is applied for custom styling
* Works seamlessly with keyboard navigation
## Related Components
* [RHFInputGroup](/docs/rhf-input-group) - Layout component that includes RHFError
* [RHFInput](/docs/rhf-input) - Text input with React Hook Form
* [RHFTextarea](/docs/rhf-textarea) - Multi-line text input
* [InputError](/docs/input-error) - Base error message component
# RHFFileInput
import { RHFFileInput } from '@geckoui/geckoui';
import {
BasicRHFFileInputExample,
WithValidationExample,
MultipleFilesExample,
AcceptTypesExample,
CustomRenderExample,
WithCallbackExample,
CompleteFormExample
} from '@/components/examples/rhf-file-input-examples';
# RHFFileInput
A file input component integrated with React Hook Form that automatically creates preview URLs for selected files. Supports single and multiple file selection with custom rendering capabilities.
## Installation
```tsx
import { RHFFileInput } from '@geckoui/geckoui';
```
## Basic Usage
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { RHFFileInput } from '@geckoui/geckoui';
function Example() {
const methods = useForm({
defaultValues: {
basicFile: undefined
}
});
return (
);
}
```
## Props API
Extends all props from standard HTML ` ` element plus:
| Prop | Type | Default | Description |
| ---------------- | ----------------------- | ------------- | ----------------------------------------------------------------- |
| `name` | `string` | - | Field name (required) |
| `control` | `Control` | Auto-injected | Optional: Pass explicitly for nested forms or custom form context |
| `rules` | `RegisterOptions` | - | Inline validation rules |
| `multiple` | `boolean` | `false` | Allow multiple file selection |
| `accept` | `string` | - | File types to accept (e.g., "image/\*", ".pdf") |
| `onChange` | `(data) => void` | - | Change callback (receives file data, not event) |
| `onBlur` | `(event) => void` | - | Blur callback |
| `render` | `ReactNode \| Function` | - | Custom render function with access to controller props |
| `className` | `string` | - | Class for wrapper element |
| `inputClassName` | `string` | - | Class for input element itself |
| `disabled` | `boolean` | `false` | Disable file input |
| ...rest | `InputHTMLAttributes` | - | All other input attributes |
## Examples
### With Validation (Zod)
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFFileInput, RHFInputGroup, Button } from '@geckoui/geckoui';
const schema = z.object({
validationAvatar: z
.instanceof(File, { message: 'Please select a file' })
.refine((file) => file.size <= 5000000, 'File size must be less than 5MB')
.refine(
(file) => ['image/jpeg', 'image/png', 'image/webp'].includes(file.type),
'Only JPEG, PNG, and WebP images are allowed'
)
});
function Example() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: {
validationAvatar: undefined
}
});
return (
);
}
```
### Multiple Files
```tsx
```
### Restrict File Types
```tsx
```
### Custom Render with Preview
```tsx
{
return (
{value ? (
) : (
Click to Upload Image
)}
);
}}
/>
```
### With onChange Callback
```tsx
const handleFileChange = (data: FileWithPreview | FileWithPreview[]) => {
console.log('File selected:', data);
if (data && !Array.isArray(data)) {
alert(`File selected: ${data.name} (${(data.size / 1024).toFixed(2)} KB)`);
}
};
```
### Complete Form Example
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFFileInput, RHFInputGroup, Button } from '@geckoui/geckoui';
const schema = z.object({
resume: z
.instanceof(File, { message: 'Resume is required' })
.refine((file) => file.size <= 10000000, 'File size must be less than 10MB')
.refine(
(file) => ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'].includes(file.type),
'Only PDF and Word documents are allowed'
),
portfolio: z
.array(z.instanceof(File))
.min(1, 'At least one portfolio item is required')
.max(5, 'Maximum 5 files allowed')
.refine(
(files) => files.every((file) => file.size <= 5000000),
'Each file must be less than 5MB'
)
.refine(
(files) => files.every((file) => ['image/jpeg', 'image/png', 'image/webp'].includes(file.type)),
'Only JPEG, PNG, and WebP images are allowed'
)
});
function ApplicationForm() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: {
resume: undefined,
portfolio: undefined
}
});
return (
);
}
```
## Inline Rules Validation
For simple validation, use the `rules` prop:
```tsx
```
For complex forms, we recommend using a schema resolver (Zod, Yup) instead.
## File Preview
RHFFileInput automatically creates preview URLs for selected files:
* Each file has a `preview` property containing an Object URL
* For single files: `value.preview`
* For multiple files: `value.map(file => file.preview)`
* Perfect for displaying image previews before upload
```tsx
(
value &&
)}
/>
```
## File Types
The `accept` prop accepts standard HTML file input values:
```tsx
accept="image/*"
accept=".pdf"
accept=".jpg,.jpeg,.png"
accept="video/*"
accept=".doc,.docx,application/pdf"
```
Common MIME types:
* Images: `image/*`, `image/jpeg`, `image/png`, `image/webp`
* Documents: `application/pdf`, `application/msword`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document`
* Videos: `video/*`, `video/mp4`
## Validation with Zod
For single file:
```tsx
z.instanceof(File, { message: 'File is required' })
.refine((file) => file.size <= 5000000, 'Max file size is 5MB')
.refine(
(file) => ['image/jpeg', 'image/png'].includes(file.type),
'Only JPEG and PNG allowed'
)
```
For multiple files:
```tsx
z.array(z.instanceof(File))
.min(1, 'At least one file required')
.max(5, 'Maximum 5 files')
.refine(
(files) => files.every((file) => file.size <= 5000000),
'Each file must be less than 5MB'
)
```
## Error States
RHFFileInput automatically displays error states with:
* Red border via `data-error` attribute on the root element when validation fails
* Use with [RHFInputGroup](/docs/rhf-input-group) for label + error message display
```css
.GeckoUIRHFFileInput[data-error] {
border-color: red;
}
```
```tsx
```
**Tip**: Use `mode: 'onBlur'` in useForm to validate after file selection:
```tsx
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur'
});
```
## Custom Rendering
The `render` prop provides full control over the UI while maintaining form integration:
```tsx
(
{field.value &&
{field.value.name}
}
{fieldState.error &&
{fieldState.error.message}
}
)}
/>
```
When using `render`, the default input is hidden but still functional.
## Accessibility
* Standard HTML file input accessibility
* Keyboard navigable (Tab to focus, Enter/Space to open)
* Screen reader compatible
* Proper ARIA attributes for error states
## Related Components
* [RHFFilePicker](/docs/rhf-file-picker) - Drag-and-drop file picker with directory support
* [RHFInputGroup](/docs/rhf-input-group) - Label + input + error wrapper
* [RHFError](/docs/rhf-error) - Error message display
# RHFFilePicker
import { RHFFilePicker } from '@geckoui/geckoui';
import {
BasicRHFFilePickerExample,
WithValidationExample,
RemoveDuplicatesExample,
KeepOldFilesExample,
CustomRenderExample,
WithCallbacksExample,
CompleteFormExample
} from '@/components/examples/rhf-file-picker-examples';
# RHFFilePicker
An advanced file picker component with drag-and-drop support, directory selection, and duplicate detection integrated with React Hook Form. Perfect for uploading multiple files or entire directories.
## Installation
```tsx
import { RHFFilePicker } from '@geckoui/geckoui';
```
## Basic Usage
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { RHFFilePicker } from '@geckoui/geckoui';
function Example() {
const methods = useForm({
defaultValues: {
basicFiles: []
}
});
return (
);
}
```
## Props API
| Prop | Type | Default | Description |
| ------------------ | --------------------------- | ------------- | -------------------------------------------------------------------------- |
| `name` | `string` | - | Field name (required) |
| `control` | `Control` | Auto-injected | Optional: Pass explicitly for nested forms or custom form context |
| `rules` | `RegisterOptions` | - | Inline validation rules |
| `accept` | `string` | `*` | File types to accept (e.g., "image/\*", ".pdf") |
| `keepOldFiles` | `boolean` | `false` | Keep previously selected files when adding new ones |
| `removeDuplicates` | `boolean` | `false` | Automatically remove duplicate files(Only works if `keepOldFiles` is true) |
| `transform` | `(files) => files` | - | Transform files before setting them |
| `onChange` | `(files, newFiles) => void` | - | Callback when files change |
| `onError` | `(error) => void` | - | Callback when an error occurs |
| `render` | `Function` | - | Custom render function with access to picker state |
## Examples
### With Validation (Zod)
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFFilePicker, RHFInputGroup, Button } from '@geckoui/geckoui';
const schema = z.object({
validationImages: z
.array(z.custom())
.min(1, 'At least one image is required')
.max(10, 'Maximum 10 images allowed')
.refine(
(files) => files.every((file) => file.size <= 5000000),
'Each file must be less than 5MB'
)
.refine(
(files) => files.every((file) => ['image/jpeg', 'image/png', 'image/webp'].includes(file.type)),
'Only JPEG, PNG, and WebP images are allowed'
)
});
function Example() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: {
validationImages: []
}
});
return (
);
}
```
### Keep Old Files
Accumulate files across multiple selections:
```tsx
```
### Remove Duplicates
If you set `keepOldFiles`, you can also enable `removeDuplicates` to avoid duplicate files:
So, if a user selects the same file multiple times, only one instance will be kept.
```tsx
```
### Custom Render with Image Grid
```tsx
{
const handleRemove = (preview: string) => {
onChange(files.filter((f) => f.preview !== preview));
};
return (
{/* Compact upload area */}
openFilePicker()}
className={`relative flex cursor-pointer items-center justify-center rounded-lg border-2 border-dashed p-4 transition-colors ${
dragging ? 'border-primary-400 bg-primary-50' : 'border-gray-300 hover:border-primary-300'
}`}
>
{dragging ? 'Drop images here' : 'Click to upload or drag and drop'}
{loading && (
)}
{/* Image grid */}
{!!files?.length && (
{files.map((file) => (
handleRemove(file.preview)}
className="absolute -right-1.5 -top-1.5 flex h-5 w-5 items-center justify-center rounded-full bg-red-500 text-white opacity-0 shadow-sm hover:bg-red-600 group-hover:opacity-100"
>
))}
)}
);
}}
/>
```
### With Callbacks
```tsx
const handleChange = (files: FilePickerFile[], newFiles: FilePickerFile[]) => {
console.log('All files:', files);
console.log('New files:', newFiles);
};
const handleError = (error: Error) => {
console.error('File picker error:', error);
alert(`Error: ${error.message}`);
};
```
### Complete Form Example
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFFilePicker, RHFInputGroup, Button } from '@geckoui/geckoui';
const schema = z.object({
projectFiles: z
.array(z.custom())
.min(1, 'At least one file is required')
.refine(
(files) => files.every((file) => file.size <= 10000000),
'Each file must be less than 10MB'
),
screenshots: z
.array(z.custom())
.min(3, 'At least 3 screenshots are required')
.max(10, 'Maximum 10 screenshots allowed')
.refine(
(files) => files.every((file) => ['image/jpeg', 'image/png', 'image/webp'].includes(file.type)),
'Only JPEG, PNG, and WebP images are allowed'
)
});
function ProjectForm() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: {
projectFiles: [],
screenshots: []
}
});
return (
);
}
```
## Inline Rules Validation
For simple validation, use the `rules` prop:
```tsx
```
For complex forms, we recommend using a schema resolver (Zod, Yup) instead.
## File Properties
Each file in the picker has these properties:
```typescript
interface FilePickerFile extends File {
preview: string;
path: string;
editableName: string;
}
```
* `preview`: Object URL for displaying file content
* `path`: File system path (from directory picker or webkitRelativePath)
* `editableName`: Editable name property (since File.name is read-only)
## Drag and Drop
RHFFilePicker automatically handles drag-and-drop:
1. Drag files over the dropzone
2. The `dragging` state becomes `true`
3. Drop files to add them
4. Files are processed and previews are created
Access drag state in custom render:
```tsx
render={({ dragging, dropzoneRef }) => (
Drop files here
)}
```
## Directory Selection
Select entire directories using the `openFilePicker` function:
```tsx
render={({ openFilePicker }) => (
openFilePicker({ directory: true })}>
Select Directory
)}
```
This preserves the directory structure in the `path` property of each file.
## Duplicate Detection
When `removeDuplicates={true}`:
* Files with identical names and sizes are considered duplicates
* Only the first occurrence is kept
* Duplicates are automatically filtered out
Files also have a `duplicatedWith` property containing an array of files they duplicate.
## File Transformation
Transform files before they're set in the form:
```tsx
const transform = async (files: FilePickerFile[]) => {
return files.map(file => {
file.editableName = file.name.toLowerCase();
return file;
});
};
```
## File Types
The `accept` prop accepts standard file input values:
```tsx
accept="image/*"
accept=".pdf"
accept=".jpg,.jpeg,.png"
accept="video/*"
accept=".doc,.docx,application/pdf"
```
## Validation with Zod
Validate file arrays with Zod:
```tsx
z.array(z.custom())
.min(1, 'At least one file required')
.max(10, 'Maximum 10 files')
.refine(
(files) => files.every((file) => file.size <= 5000000),
'Each file must be less than 5MB'
)
.refine(
(files) => files.every((file) =>
['image/jpeg', 'image/png'].includes(file.type)
),
'Only JPEG and PNG allowed'
)
```
## Error States
RHFFilePicker automatically displays error states:
* Red border via `data-error` attribute on the root element when validation fails
* Use with [RHFInputGroup](/docs/rhf-input-group) for complete error display
```tsx
```
**Tip**: Use `mode: 'onBlur'` to validate after file selection:
```tsx
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur'
});
```
## Loading States
The picker automatically manages loading states:
```tsx
render={({ loading }) => (
loading ? : Ready to upload
)}
```
Loading occurs during:
* File processing
* Preview URL generation
* Transform function execution
## Custom Rendering
The `render` prop provides access to picker state:
```tsx
render={({
dropzoneRef,
dragging,
loading,
openFilePicker,
files,
field,
fieldState,
formState
}) => (
{/* Custom UI */}
)}
```
**Important**: Always attach `dropzoneRef` to your dropzone element for drag-and-drop to work.
## Accessibility
* Keyboard navigable buttons
* Screen reader compatible
* Proper ARIA attributes for loading and error states
* Focus management for file picker dialogs
## Performance Tips
1. Use `removeDuplicates` to prevent duplicate uploads
2. Set file size limits in validation
3. Limit file count with Zod `.max()`
4. Use `accept` to restrict file types before selection
## Related Components
* [RHFFileInput](/docs/rhf-file-input) - Simple file input for single/multiple files
* [RHFInputGroup](/docs/rhf-input-group) - Label + input + error wrapper
* [RHFError](/docs/rhf-error) - Error message display
# RHFInputGroup
import { RHFInputGroup } from '@geckoui/geckoui';
import {
BasicRHFInputGroupExample,
WithRequiredExample,
WithHelpTextExample,
WithTextareaExample,
WithNestedStructureExample,
WithCustomStylingExample,
CompleteFormGroupExample
} from '@/components/examples/rhf-input-group-examples';
# RHFInputGroup
A layout convenience component that combines a form field label, input component, and error message into a cohesive unit. Automatically detects RHF input components in children (even nested) and extracts their name and control to display validation errors. Reduces boilerplate for standard form field layouts.
> **Recommended Pattern**: Use `RHFInputGroup` when you need label and input detection. It automatically handles the label-input association and error display, reducing boilerplate.
## Installation
```tsx
import { RHFInputGroup } from '@geckoui/geckoui';
```
## Basic Usage
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { RHFInput, RHFInputGroup } from '@geckoui/geckoui';
function Example() {
const methods = useForm({
defaultValues: {
basicUsername: ''
}
});
return (
);
}
```
## Props API
Extends all props from [Label](/docs/label) component plus:
| Prop | Type | Default | Description |
| ---------------- | ------------ | ------- | ------------------------------------- |
| `label` | `string` | - | Label text for the input group |
| `labelClassName` | `string` | - | Class name for the label element |
| `className` | `string` | - | Class name for the wrapper div |
| `errorClassName` | `string` | - | Class name for the error message |
| `children` | `ReactNode` | - | Input component(s) to wrap (required) |
| `required` | `boolean` | - | Shows required asterisk (\*) on label |
| `tooltip` | `string` | - | Tooltip text shown on label hover |
| ...rest | `LabelProps` | - | All Label component props |
## Examples
### With Required Field
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFInput, RHFInputGroup } from '@geckoui/geckoui';
const schema = z.object({
groupEmail: z.string().email('Invalid email address').min(1, 'Email is required')
});
function Example() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: { groupEmail: '' }
});
return (
);
}
```
### With Help Text (Tooltip)
```tsx
```
### With Textarea
```tsx
import { RHFTextarea } from '@geckoui/geckoui';
const schema = z.object({
groupBio: z.string().min(10, 'Bio must be at least 10 characters').max(200, 'Bio must be less than 200 characters')
});
```
### With Nested Structure
The component automatically finds the first RHF input component even when nested:
```tsx
const schema = z.object({
nestedPassword: z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[0-9]/, 'Password must contain at least one number')
.regex(/[!@#$%^&*]/, 'Password must contain at least one special character')
});
â At least 8 characters
â Include a number
â Include a special character
```
### With Custom Styling
```tsx
```
### Complete Form Example
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFInput, RHFTextarea, RHFInputGroup, Button } from '@geckoui/geckoui';
const schema = z.object({
formUsername: z.string().min(3, 'Username must be at least 3 characters'),
formEmail: z.string().email('Invalid email address'),
formBio: z.string().min(10, 'Bio must be at least 10 characters'),
formPassword: z.string().min(8, 'Password must be at least 8 characters')
});
function RegistrationForm() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: {
formUsername: '',
formEmail: '',
formBio: '',
formPassword: ''
}
});
const onSubmit = (data: any) => {
console.log('Form data:', data);
alert('Form submitted! Check console for data.');
};
return (
);
}
```
## How It Works
RHFInputGroup automatically:
1. **Finds the RHF input component** - Recursively searches children to find the first component with `displayName` containing "rhf" (case-insensitive)
2. **Extracts field name and control** - Gets the `name` and `control` props from the detected input
3. **Renders error messages** - Automatically passes these to [RHFError](/docs/rhf-error) component to display validation errors
This means you don't need to manually specify the field name twice:
```tsx
```
Instead of:
```tsx
Email
```
## Multiple Inputs
When multiple inputs are present, only the **first input's errors** are shown:
```tsx
```
For multiple inputs with individual error messages, use [RHFError](/docs/rhf-error) manually:
```tsx
```
## Best Practices
1. **Use for standard layouts** - Perfect for typical label + input + error patterns
2. **Custom layouts** - Use [Label](/docs/label) and [RHFError](/docs/rhf-error) separately for complex layouts
3. **Validation mode** - Use `mode: 'onBlur'` in useForm for better UX:
```tsx
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur' // Validates when user leaves the field
});
```
4. **Required fields** - Always use the `required` prop to show asterisk on labels
5. **Help text** - Use `tooltip` prop for additional context without cluttering the UI
## Accessibility
* Inherits all accessibility features from [Label](/docs/label) component
* Proper `htmlFor` attribute linking label to input
* ARIA attributes for required fields
* Error messages properly associated with inputs
* Keyboard navigable
* Screen reader compatible
## Related Components
* [RHFError](/docs/rhf-error) - Error message display component
* [RHFInput](/docs/rhf-input) - Text input with React Hook Form
* [RHFTextarea](/docs/rhf-textarea) - Multi-line text input
* [Label](/docs/label) - Base label component
# RHFInput
import { RHFInput } from '@geckoui/geckoui';
import {
BasicRHFInputExample,
WithValidationExample,
WithPrefixSuffixExample,
WithTransformExample,
WithCustomSuffixExample,
DisabledExample,
CompleteFormExample
} from '@/components/examples/rhf-input-examples';
# RHFInput
A text input component integrated with React Hook Form that automatically displays error states with a red border when validation fails. Supports value transformation for input/output formatting.
## Installation
```tsx
import { RHFInput } from '@geckoui/geckoui';
```
## Basic Usage
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { RHFInput } from '@geckoui/geckoui';
function Example() {
const methods = useForm({
defaultValues: {
username: ''
}
});
return (
);
}
```
## Props API
Extends all props from [Input](/docs/input) component plus:
| Prop | Type | Default | Description | |
| ----------- | ------------------------- | ------------- | --------------------------------------------------------------------------------- | --------------------------------------------- |
| `name` | `string` | - | Field name (required) | |
| `control` | `Control` | Auto-injected | Optional: Pass explicitly for nested forms or custom form context | |
| `rules` | `RegisterOptions` | - | Inline validation rules (see [Inline Rules Validation](#inline-rules-validation)) | |
| `transform` | `{ input, output }` | - | Value transformation functions | |
| `prefix` | \`ReactNode \\ | Function\` | - | Element before input (can access field state) |
| `suffix` | \`ReactNode \\ | Function\` | - | Element after input (can access field state) |
| `onChange` | `(value: string) => void` | - | Change callback (receives value, not event) | |
| `onBlur` | `(value: string) => void` | - | Blur callback | |
| ...rest | `InputProps` | - | All Input component props | |
## Examples
### With Validation (Zod)
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFInput, RHFInputGroup, Button } from '@geckoui/geckoui';
const schema = z.object({
email: z.string().email('Invalid email address').min(1, 'Email is required')
});
function Example() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur', // Validate on blur
defaultValues: { email: '' }
});
return (
);
}
```
### Inline Rules Validation
For simple forms, you can use the `rules` prop for inline validation instead of a schema resolver:
```tsx
```
**Note:** For complex forms with many fields, we recommend using a schema resolver (Zod, Yup) instead of inline rules for better maintainability and type safety.
### With Prefix and Suffix
```tsx
```
### With Value Transformation
Transform values on input/output (e.g., phone number formatting):
```tsx
const formatPhone = (value: string) => {
const cleaned = value.replace(/\\D/g, '');
const match = cleaned.match(/^(\\d{3})(\\d{3})(\\d{4})$/);
if (match) {
return `(${match[1]}) ${match[2]}-${match[3]}`;
}
return value;
};
const sanitizePhone = (value: string) => {
return value.replace(/\\D/g, '');
};
```
### Custom Suffix with Error Icon
```tsx
<>fieldState.error ? : null>}
/>
```
### Disabled State
```tsx
```
### Complete Form Example
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFInput, RHFInputGroup, Button } from '@geckoui/geckoui';
const schema = z.object({
username: z.string().min(3, 'Username must be at least 3 characters'),
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters')
});
function RegistrationForm() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur', // Validate on blur
defaultValues: {
username: '',
email: '',
password: ''
}
});
return (
);
}
```
## Error States
RHFInput automatically displays error states with:
* Red border via `data-error` attribute on the root element when `fieldState.error` exists
* No automatic error icon (you must add via suffix prop if desired)
```css
.GeckoUIRHFInput[data-error] {
border-color: red;
}
```
**Recommended**: Use [RHFInputGroup](/docs/rhf-input-group) for label + input + error layouts. It automatically detects the RHF input and displays validation errors:
```tsx
```
Or combine with [RHFError](/docs/rhf-error) component manually:
```tsx
```
**Tip**: Use `mode: 'onBlur'` in useForm to validate on blur for better UX:
```tsx
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur' // Validates when user leaves the field
});
```
## Value Transformation
The `transform` prop allows formatting values differently for display vs storage:
```tsx
// Currency formatting
value ? `$${value}` : '',
output: (value) => value.replace('$', '')
}}
/>
// Uppercase transformation
value?.toUpperCase() || '',
output: (value) => value
}}
/>
```
## Dynamic Prefix/Suffix
Both `prefix` and `suffix` can be functions that receive render props:
```tsx
(
field.value ? :
)}
suffix={({ fieldState }) => (
fieldState.error ? : null
)}
/>
```
## Accessibility
* Inherits all accessibility features from [Input](/docs/input) component
* Proper ARIA attributes for error states
* Keyboard navigable
* Screen reader compatible
## Related Components
* [Input](/docs/input) - Base input component
* [RHFTextarea](/docs/rhf-textarea) - Multi-line text input
* [RHFNumberInput](/docs/rhf-number-input) - Number-only input
* [RHFError](/docs/rhf-error) - Error message display
# RHFOTPInput
import { RHFOTPInput } from '@geckoui/geckoui';
import {
BasicRHFOTPInputExample,
WithValidationExample,
SixDigitExample,
FourDigitExample,
AlphanumericExample,
DisabledExample,
CompleteFormExample
} from '@/components/examples/rhf-otp-input-examples';
# RHFOTPInput
An OTP (One-Time Password) input component integrated with React Hook Form that provides a segmented input field for entering verification codes. Each digit is displayed in a separate box with automatic focus management and keyboard navigation. Automatically displays error states with a red border when validation fails.
## Installation
```tsx
import { RHFOTPInput } from '@geckoui/geckoui';
```
## Basic Usage
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { RHFOTPInput } from '@geckoui/geckoui';
function Example() {
const methods = useForm({
defaultValues: {
basicOtp: ''
}
});
return (
);
}
```
## Props API
Extends all props from base OTPInput component plus:
| Prop | Type | Default | Description |
| ---------------- | ------------------------- | ------------- | ----------------------------------------------------------------- |
| `name` | `string` | - | Field name (required) |
| `control` | `Control` | Auto-injected | Optional: Pass explicitly for nested forms or custom form context |
| `rules` | `RegisterOptions` | - | Inline validation rules |
| `length` | `number` | 6 | Number of OTP input fields to display |
| `numberOnly` | `boolean` | true | If true, only numeric input is allowed |
| `onOTPComplete` | `(value: string) => void` | - | Callback when all OTP fields are filled |
| `className` | `string` | - | Wrapper div classname |
| `inputClassName` | `string` | - | Individual input field classname |
| `aspectRatio` | `string \| number` | 0.94 | Aspect ratio for each input box |
| `disabled` | `boolean` | false | Disable all input fields |
## Examples
### With Validation (Zod)
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFOTPInput, RHFInputGroup } from '@geckoui/geckoui';
const schema = z.object({
validationCode: z.string().length(6, 'OTP must be exactly 6 digits').regex(/^\d+$/, 'OTP must contain only numbers')
});
function Example() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: { validationCode: '' }
});
return (
);
}
```
### Six Digit Code
```tsx
```
### Four Digit PIN
```tsx
```
### Alphanumeric Code
```tsx
const schema = z.object({
activationCode: z.string().length(8, 'Activation code must be exactly 8 characters')
});
```
### Disabled State
```tsx
```
### Complete Form Example
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFOTPInput, RHFInputGroup, Button } from '@geckoui/geckoui';
const schema = z.object({
twoFactorCode: z.string().length(6, 'Code must be exactly 6 digits').regex(/^\d+$/, 'Code must contain only numbers')
});
function TwoFactorForm() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: {
twoFactorCode: ''
}
});
return (
);
}
```
## Inline Rules Validation
For simple validation, use the `rules` prop:
```tsx
```
For complex forms, we recommend using a schema resolver (Zod, Yup) instead.
## Length Configuration
Configure the number of OTP input fields using the `length` prop:
```tsx
```
By default, `length` is set to 6 digits.
## Input Mode
Control whether users can enter only numbers or alphanumeric characters:
```tsx
```
By default, `numberOnly` is `true`, restricting input to numeric characters only.
## OTP Completion Callback
Use `onOTPComplete` to perform actions when all OTP fields are filled:
```tsx
{
console.log('OTP entered:', value);
verifyCode(value);
}}
/>
```
This callback is triggered automatically when the user completes entering all digits.
## Error States
RHFOTPInput automatically displays error states with:
* Red border via `data-error` attribute on the root element when `fieldState.error` exists
* Individual input fields inherit error styling
```css
.GeckoUIRHFOTPInput[data-error] {
border-color: red;
}
```
Use [RHFInputGroup](/docs/rhf-input-group) component for label + input + error layout:
```tsx
```
Or combine with [RHFError](/docs/rhf-error) component manually:
```tsx
```
**Tip**: Use `mode: 'onBlur'` in useForm to validate on blur for better UX:
```tsx
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur'
});
```
## Validation Patterns
Common Zod validation patterns for OTP inputs:
```tsx
const schema = z.object({
sixDigitCode: z.string()
.length(6, 'Code must be exactly 6 digits')
.regex(/^\d+$/, 'Code must contain only numbers'),
fourDigitPin: z.string()
.length(4, 'PIN must be exactly 4 digits')
.regex(/^\d+$/, 'PIN must contain only numbers'),
alphanumericCode: z.string()
.length(8, 'Code must be exactly 8 characters')
.regex(/^[A-Za-z0-9]+$/, 'Code must be alphanumeric')
});
```
## Accessibility
* Automatic focus management between input fields
* Keyboard navigation support (arrow keys, backspace, delete)
* Proper ARIA attributes for error states
* Tab navigation support with smart focus handling
* Input mode hints for mobile keyboards (numeric vs text)
* Screen reader compatible
## Keyboard Navigation
* **Numbers/Letters**: Enter value and auto-advance to next field
* **Backspace/Delete**: Remove current value and move to previous field
* **Tab**: Navigate to next unfilled field
* **Enter**: Submit form when last field is filled
* **Paste**: Automatically distribute pasted content across fields
## Related Components
* [OTPInput](/docs/otp-input) - Base OTP input component
* [RHFInput](/docs/rhf-input) - Text input with RHF
* [RHFInputGroup](/docs/rhf-input-group) - Label + input + error layout
* [RHFError](/docs/rhf-error) - Error message display
# RHFRadio
import { RHFRadio } from '@geckoui/geckoui';
import {
BasicRHFRadioExample,
WithValidationExample,
WithStringValuesExample,
WithBooleanValuesExample,
WithObjectValuesExample,
WithArrayValuesExample,
DisabledExample,
CompleteFormExample
} from '@/components/examples/rhf-radio-examples';
# RHFRadio
A controlled radio button component for React Hook Form that preserves value types. Unlike native radio inputs that convert values to strings, this component maintains the original data type. Perfect for creating mutually exclusive selection groups.
## Installation
```tsx
import { RHFRadio } from '@geckoui/geckoui';
```
## Basic Usage
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { RHFRadio } from '@geckoui/geckoui';
function Example() {
const methods = useForm({
defaultValues: {
plan: 'free'
}
});
return (
);
}
```
## Props API
Extends all props from [Radio](/docs/radio) component plus:
| Prop | Type | Default | Description |
| ---------------- | -------------------------- | ------------- | ---------------------------------------------------------------------- |
| `name` | `string` | - | Field name (required) |
| `control` | `Control` | Auto-injected | Optional: Pass explicitly for nested forms or custom form context |
| `rules` | `RegisterOptions` | - | Inline validation rules |
| `label` | `ReactNode \| Function` | - | Label displayed next to radio |
| `labelClassName` | `string` | - | CSS class for the label |
| `value` | `unknown` | - | Value to set when selected (required, cannot be `null` or `undefined`) |
| `onChange` | `(value: unknown) => void` | - | Change callback |
| ...rest | `RadioProps` | - | All Radio component props |
## Examples
### With Validation (Zod)
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFRadio, RHFInputGroup } from '@geckoui/geckoui';
const schema = z.object({
plan: z.enum(['free', 'pro', 'enterprise']).refine((val) => val !== undefined, {
message: 'Please select a plan'
})
});
function Example() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: { plan: undefined }
});
return (
);
}
```
### With String Values
```tsx
```
### With Boolean Values
```tsx
```
### With Object Values
```tsx
```
### With Array Values
```tsx
```
### Disabled State
```tsx
```
### Complete Form Example
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFRadio, RHFInputGroup, Button } from '@geckoui/geckoui';
const schema = z.object({
plan: z.enum(['free', 'pro', 'enterprise']).refine((val) => val !== undefined, {
message: 'Please select a plan'
}),
paymentMethod: z.enum(['card', 'paypal', 'bank']).refine((val) => val !== undefined, {
message: 'Please select a payment method'
}),
billingCycle: z.enum(['monthly', 'yearly'])
});
function FormExample() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: {
plan: undefined,
paymentMethod: undefined,
billingCycle: 'monthly'
}
});
return (
);
}
```
## Inline Rules Validation
For simple validation, use the `rules` prop:
```tsx
```
For complex forms, we recommend using a schema resolver (Zod, Yup) instead.
## Value Type Preservation
Unlike native HTML radio buttons that convert all values to strings, RHFRadio preserves the original value type:
```tsx
// String values
// Boolean values
// Number values
// Object values
// Array values
```
The form data will contain the exact value you passed, maintaining its type:
```tsx
// Form submission result:
{
plan: "pro", // string
enabled: true, // boolean
quantity: 10, // number
config: { theme: "dark", lang: "en" }, // object
permissions: ['read', 'write'] // array
}
```
## Radio Groups
Radio buttons with the same `name` prop are automatically grouped. Only one radio button in a group can be selected at a time:
```tsx
// All these radios share the same name, creating a mutually exclusive group
```
## Value Requirement
The `value` prop is required and cannot be `undefined` or `null`. If you try to use these values, RHFRadio will throw an error:
```tsx
// â Error: value cannot be undefined or null
// â
Correct: Use any other value type
```
## Error States
RHFRadio doesn't apply error styling directly. Use [RHFInputGroup](/docs/rhf-input-group) to display errors for the group:
```tsx
```
**Tip**: Use `mode: 'onBlur'` in useForm to validate on blur:
```tsx
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur'
});
```
## Accessibility
* Inherits all accessibility features from [Radio](/docs/radio) component
* Proper ARIA attributes
* Keyboard navigable (Arrow keys navigate within group, Space selects)
* Screen reader compatible
* Disabled state properly communicated
## Related Components
* [Radio](/docs/radio) - Base radio component
* [RHFCheckbox](/docs/rhf-checkbox) - Multi-select checkboxes
* [RHFSwitch](/docs/rhf-switch) - Toggle switch
# RHFSelect
import { RHFSelect, SelectOption } from '@geckoui/geckoui';
import {
BasicRHFSelectExample,
WithValidationExample,
MultipleSelectExample,
WithGroupsExample,
DisabledExample,
WithOnChangeExample,
CompleteFormExample
} from '@/components/examples/rhf-select-examples';
# RHFSelect
A select dropdown component integrated with React Hook Form that automatically displays error states with a red border when validation fails. Supports both single and multiple selection modes with keyboard navigation and search capabilities.
## Installation
```tsx
import { RHFSelect, SelectOption } from '@geckoui/geckoui';
```
## Basic Usage
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { RHFSelect, SelectOption } from '@geckoui/geckoui';
function Example() {
const methods = useForm({
defaultValues: {
basicCountry: ''
}
});
return (
);
}
```
## Props API
Extends all props from [Select](/docs/select) component plus:
| Prop | Type | Default | Description |
| ------------------ | --------------------------- | ------------- | ----------------------------------------------------------------- |
| `name` | `string` | - | Field name (required) |
| `control` | `Control` | Auto-injected | Optional: Pass explicitly for nested forms or custom form context |
| `rules` | `RegisterOptions` | - | Inline validation rules |
| `multiple` | `boolean` | `false` | Enable multiple selection mode |
| `children` | `SelectOption[]` | - | SelectOption components to render as dropdown options |
| `onChange` | `(value: T \| T[]) => void` | - | Change callback (receives value based on single/multiple mode) |
| `className` | `string` | - | Additional CSS class for the select button |
| `wrapperClassName` | `string` | - | Additional CSS class for the wrapper div |
| `placeholder` | `string` | - | Placeholder text when no value is selected |
| `disabled` | `boolean` | `false` | Disable the select input |
| ...rest | `SelectProps` | - | All Select component props |
## Examples
### With Validation (Zod)
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFSelect, SelectOption, RHFInputGroup } from '@geckoui/geckoui';
const schema = z.object({
validationSkills: z.string().min(1, 'Please select at least one skill')
});
function Example() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: {
validationSkills: ''
}
});
return (
);
}
```
### Multiple Selection
Enable multiple selection mode to allow users to select multiple options:
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { RHFSelect, SelectOption } from '@geckoui/geckoui';
function Example() {
const methods = useForm({
defaultValues: {
multipleLanguages: []
}
});
return (
);
}
```
### With Option Groups
Organize options into logical groups:
```tsx
```
### Disabled State
```tsx
```
### With onChange Callback
Execute custom logic when the selection changes:
```tsx
{
console.log('Selected priority:', value);
}}
>
```
### Complete Form Example
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFSelect, SelectOption, RHFInputGroup, Button } from '@geckoui/geckoui';
const schema = z.object({
formCountry: z.string().min(1, 'Country is required'),
formLanguages: z.array(z.string()).min(1, 'Select at least one language'),
formPriority: z.string().min(1, 'Priority is required')
});
function RegistrationForm() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: {
formCountry: '',
formLanguages: [],
formPriority: ''
}
});
return (
);
}
```
## Inline Rules Validation
For simple validation, use the `rules` prop:
```tsx
```
For complex forms, we recommend using a schema resolver (Zod, Yup) instead.
## Single vs Multiple Mode
RHFSelect supports two selection modes:
### Single Select (Default)
```tsx
const methods = useForm({
defaultValues: {
country: ''
}
});
```
In single select mode:
* Value is a single item of type `T`
* Clicking an option selects it and closes the menu
* Only one option can be selected at a time
### Multiple Select
```tsx
const methods = useForm({
defaultValues: {
languages: []
}
});
```
In multiple select mode:
* Value is an array of type `T[]`
* Clicking an option toggles its selection
* Menu stays open after selection
* Multiple options can be selected
## Validation
Use Zod schema validation for form validation:
```tsx
const schema = z.object({
singleSelect: z.string().min(1, 'This field is required'),
multipleSelect: z.array(z.string()).min(1, 'Select at least one option')
});
```
For more complex validation:
```tsx
const schema = z.object({
skills: z
.array(z.string())
.min(2, 'Select at least 2 skills')
.max(5, 'Select no more than 5 skills')
});
```
**Tip**: Use `mode: 'onBlur'` in useForm to validate when the user leaves the field:
```tsx
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur'
});
```
## Error States
RHFSelect automatically displays error states with:
* Red border via `data-error` attribute on the root element when `fieldState.error` exists
* No automatic error message display (use RHFInputGroup or RHFError component)
```css
.GeckoUIRHFSelectButton[data-error] {
border-color: red;
}
```
Use [RHFInputGroup](/docs/rhf-input-group) component for label + select + error layout:
```tsx
```
Or combine with [RHFError](/docs/rhf-error) component manually:
```tsx
```
## Accessibility
* Full keyboard navigation support (Arrow keys, Enter, Escape)
* Built-in search/filter functionality
* Proper ARIA attributes for error states
* Screen reader compatible
* Focus management with visual indicators
## Related Components
* [Select](/docs/select) - Base select component
* [SelectOption](/docs/select-option) - Individual option component
* [RHFInputGroup](/docs/rhf-input-group) - Label and error wrapper
* [RHFError](/docs/rhf-error) - Error message display
# RHFSwitch
import { RHFSwitch } from '@geckoui/geckoui';
import {
BasicRHFSwitchExample,
WithCustomValuesExample,
WithNumberValuesExample,
WithObjectValuesExample,
DifferentSizesExample,
DisabledExample,
WithValidationExample,
CompleteFormExample
} from '@/components/examples/rhf-switch-examples';
# RHFSwitch
A controlled toggle switch component for React Hook Form. By default stores boolean values (true/false), but supports custom values of any type. Values maintain their original data types.
## Installation
```tsx
import { RHFSwitch } from '@geckoui/geckoui';
```
## Basic Usage
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { RHFSwitch } from '@geckoui/geckoui';
function Example() {
const methods = useForm({
defaultValues: {
enabled: false
}
});
return (
Enable notifications
);
}
```
## Props API
Extends all props from [Switch](/docs/switch) component plus:
| Prop | Type | Default | Description |
| ---------------- | -------------------------- | ------------- | ----------------------------------------------------------------- |
| `name` | `string` | - | Field name (required) |
| `control` | `Control` | Auto-injected | Optional: Pass explicitly for nested forms or custom form context |
| `rules` | `RegisterOptions` | - | Inline validation rules |
| `value` | `unknown` | - | Value when checked (if not provided, uses boolean) |
| `uncheckedValue` | `unknown` | - | Value when unchecked (only with `value` prop) |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Switch size |
| `onChange` | `(value: unknown) => void` | - | Change callback |
| ...rest | `SwitchProps` | - | All Switch component props |
## Examples
### With Custom String Values
```tsx
```
### With Number Values
```tsx
```
### With Object Values
```tsx
```
### Different Sizes
```tsx
```
### Disabled State
```tsx
```
### With Validation (Zod)
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFSwitch, RHFInputGroup } from '@geckoui/geckoui';
const schema = z.object({
terms: z.literal(true, { message: 'You must accept the terms' })
});
function Example() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: { terms: false }
});
return (
I accept the terms and conditions
);
}
```
### Complete Form Example
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFSwitch, RHFInputGroup, Button } from '@geckoui/geckoui';
const schema = z.object({
notifications: z.boolean(),
marketing: z.boolean(),
darkMode: z.boolean(),
twoFactor: z.literal(true, { message: 'Two-factor authentication is required' })
});
function FormExample() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: {
notifications: true,
marketing: false,
darkMode: false,
twoFactor: false
}
});
return (
);
}
```
## Inline Rules Validation
For simple validation, use the `rules` prop:
```tsx
```
For complex forms, we recommend using a schema resolver (Zod, Yup) instead.
## Value Behavior
### Default (Boolean Mode)
When `value` prop is not provided, the switch toggles between `true` and `false`:
```tsx
// Stores: true or false
```
### Custom Values Mode
When `value` prop is provided:
* **Checked**: Stores the `value`
* **Unchecked**: Stores `uncheckedValue` (default: `undefined`)
```tsx
// Stores: "yes" or "no"
// Stores: 1 or 0
// Stores: object or undefined
// Stores: first object or second object
```
## Value Type Preservation
RHFSwitch preserves the original value type:
```tsx
// Boolean (default)
// Result: true or false
// String
// Result: "active" or "inactive"
// Number
// Result: 10 or 0
// Object
// Result: { a: 1 } or { a: 0 }
// Array
// Result: ['read', 'write'] or []
```
## Error States
RHFSwitch doesn't apply error styling directly. Use [RHFInputGroup](/docs/rhf-input-group) to display errors:
```tsx
Accept terms
```
**Tip**: Use `mode: 'onBlur'` in useForm to validate on blur:
```tsx
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur'
});
```
## Keyboard Support
The switch supports full keyboard interaction:
* **Space** or **Enter**: Toggle the switch
* **Tab**: Move focus to/from the switch
## Accessibility
* Inherits all accessibility features from [Switch](/docs/switch) component
* Proper ARIA attributes
* Keyboard accessible
* Screen reader compatible
* Disabled state properly communicated
## Related Components
* [Switch](/docs/switch) - Base switch component
* [RHFCheckbox](/docs/rhf-checkbox) - Checkbox component
* [RHFRadio](/docs/rhf-radio) - Radio button component
# RHFTextarea
import { RHFTextarea } from '@geckoui/geckoui';
import {
BasicRHFTextareaExample,
WithValidationExample,
AutoResizeExample,
WithRowConstraintsExample,
DisabledExample,
CompleteFormExample
} from '@/components/examples/rhf-textarea-examples';
# RHFTextarea
A multi-line textarea component integrated with React Hook Form that automatically displays error states with a red border when validation fails. Supports auto-resizing functionality.
## Installation
```tsx
import { RHFTextarea } from '@geckoui/geckoui';
```
## Basic Usage
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { RHFTextarea } from '@geckoui/geckoui';
function Example() {
const methods = useForm({
defaultValues: {
description: ''
}
});
return (
);
}
```
## Props API
Extends all props from [Textarea](/docs/textarea) component plus:
| Prop | Type | Default | Description |
| ------------ | ------------------------- | ------------- | ----------------------------------------------------------------- |
| `name` | `string` | - | Field name (required) |
| `control` | `Control` | Auto-injected | Optional: Pass explicitly for nested forms or custom form context |
| `rules` | `RegisterOptions` | - | Inline validation rules |
| `autoResize` | `boolean` | `false` | Enable automatic height adjustment |
| `rows` | `number` | `2` | Minimum number of rows |
| `maxRows` | `number` | - | Maximum number of rows (only with `autoResize`) |
| `onChange` | `(value: string) => void` | - | Change callback (receives value, not event) |
| `onBlur` | `(value: string) => void` | - | Blur callback |
| ...rest | `TextareaProps` | - | All Textarea component props |
## Examples
### With Validation (Zod)
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFTextarea, RHFInputGroup } from '@geckoui/geckoui';
const schema = z.object({
bio: z.string().min(10, 'Bio must be at least 10 characters').max(500, 'Bio must not exceed 500 characters')
});
function Example() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: { bio: '' }
});
return (
);
}
```
### Auto-Resizing Textarea
```tsx
```
### With Row Constraints
```tsx
```
### Disabled State
```tsx
```
### Complete Form Example
```tsx
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { RHFTextarea, RHFInputGroup, Button } from '@geckoui/geckoui';
const schema = z.object({
title: z.string().min(1, 'Title is required'),
description: z.string().min(20, 'Description must be at least 20 characters'),
notes: z.string().optional()
});
function FormExample() {
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: {
title: '',
description: '',
notes: ''
}
});
return (
);
}
```
## Inline Rules Validation
For simple validation, use the `rules` prop:
```tsx
```
For complex forms, we recommend using a schema resolver (Zod, Yup) instead.
## Error States
RHFTextarea automatically displays error states with:
* Red border via `data-error` attribute on the root element when `fieldState.error` exists
```css
.GeckoUIRHFTextarea[data-error] {
border-color: red;
}
```
Use [RHFInputGroup](/docs/rhf-input-group) component for label + textarea + error layout:
```tsx
```
Or combine with [RHFError](/docs/rhf-error) component manually:
```tsx
```
**Tip**: Use `mode: 'onBlur'` in useForm to validate on blur for better UX:
```tsx
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onBlur'
});
```
## Auto-Resize Behavior
When `autoResize` is enabled:
* Textarea grows automatically as content increases
* Minimum height is determined by the `rows` prop
* Maximum height is determined by the `maxRows` prop
* Shrinks back down when content is removed
When `autoResize` is disabled:
* Textarea has a fixed height based on `rows`
* Users can resize manually (if CSS allows)
* The `maxRows` prop is ignored
## Accessibility
* Inherits all accessibility features from [Textarea](/docs/textarea) component
* Proper ARIA attributes for error states
* Keyboard navigable
* Screen reader compatible
## Related Components
* [Textarea](/docs/textarea) - Base textarea component
* [RHFInput](/docs/rhf-input) - Single-line text input
* [RHFError](/docs/rhf-error) - Error message display
# Spinner
import { Spinner } from '@geckoui/geckoui';
# Spinner
A loading spinner icon component that displays an animated circular indicator. The spinner uses the current text color (via `currentColor`) for its stroke, making it easy to theme by changing the text color of its parent element.
## Installation
```tsx
import { Spinner } from '@geckoui/geckoui';
```
## Basic Usage
```tsx
```
## Props API
| Prop | Type | Default | Description |
| ----------- | --------------- | ---------------- | --------------------------- |
| `stroke` | `string` | `'currentColor'` | SVG stroke color |
| `className` | `string` | - | CSS class for styling |
| ...rest | `SVGAttributes` | - | All standard SVG attributes |
## Examples
### Different Sizes
```tsx
```
### Custom Colors
```tsx
```
### Loading States
Loading...
Processing your request...
Saving changes...
```tsx
Loading...
Processing your request...
```
### Centered Loading
```tsx
```
### In Buttons
Loading...
Processing
```tsx
Loading...
```
### Full Page Loading
```tsx
function FullPageLoading() {
return (
);
}
```
### With LoadingButton
Saving...
```tsx
import { LoadingButton } from '@geckoui/geckoui';
Save Changes
```
### Inline with Text
Fetching latest data...
Syncing with server...
```tsx
Fetching latest data...
```
## Usage Patterns
### Conditional Rendering
```tsx
function DataComponent() {
const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
if (loading) {
return (
);
}
return {/* Render data */}
;
}
```
### With Async Operations
```tsx
function AsyncButton() {
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
await performAsyncOperation();
setLoading(false);
};
return (
{loading ? (
<>
Processing...
>
) : (
Click Me
)}
);
}
```
## Accessibility
* Uses semantic SVG with `role="status"`
* Includes `aria-label="Loading"` for screen readers
* Animation provides visual feedback
* Should be accompanied by text for context
## Styling
The component uses the class name `GeckoUISpinnerIcon`. The spinner animation is applied via CSS:
```css
.GeckoUISpinnerIcon {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
```
## Color Theming
The spinner uses `currentColor` by default, inheriting the text color from its parent:
```tsx
// Inherits blue color from parent
// Direct color application
// Via stroke prop
```
## Related Components
* [LoadingButton](/docs/loading-button) - Button with built-in spinner
* [Alert](/docs/alert) - Status messages
# Switch
import { Switch } from '@geckoui/geckoui';
import { SwitchBasicExample, SwitchSizesExample, SwitchSettingsPanelExample, SwitchDisabledExample } from '@/components/examples/switch-examples';
import { SwitchCustomSizeExample } from '@/components/examples/module-augmentation-examples';
# Switch
A toggle component built on HeadlessUI that provides an accessible on/off control. Supports multiple sizes, controlled and uncontrolled modes, and integrates seamlessly with forms.
## Installation
```tsx
import { Switch } from '@geckoui/geckoui';
```
## Basic Usage
```tsx
import { useState } from 'react';
function Example() {
const [enabled, setEnabled] = useState(false);
return (
);
}
```
## Props API
| Prop | Type | Default | Description |
| ---------------- | ---------------------------- | ------- | ------------------------------------ |
| `checked` | `boolean` | - | Controlled checked state |
| `onChange` | `(checked: boolean) => void` | - | Callback when state changes |
| `size` | `'sm' \| 'md'` | `'md'` | Size of the switch |
| `className` | `string` | - | CSS class for the switch container |
| `thumbClassName` | `string` | - | CSS class for the switch thumb |
| `disabled` | `boolean` | `false` | Disable the switch |
| `defaultChecked` | `boolean` | - | Initial checked state (uncontrolled) |
## Examples
### Sizes
```tsx
```
### Settings Panel
```tsx
function SettingsPanel() {
const [settings, setSettings] = useState({
notifications: true,
darkMode: false,
analytics: true,
});
return (
Email Notifications
Receive email updates
setSettings({ ...settings, notifications: checked })
}
/>
{/* More settings... */}
);
}
```
### Disabled State
```tsx
```
### Uncontrolled with Default Value
```tsx
```
## Keyboard Navigation
The switch supports keyboard interaction:
* **Space** or **Enter**: Toggle the switch
* **Tab**: Move focus to next element
* **Shift + Tab**: Move focus to previous element
Note: The Enter key is prevented from default form submission behavior.
## Module Augmentation
The Switch component supports TypeScript module augmentation, allowing you to add custom sizes with full type safety.
### Live Example
A custom `lg` size created using module augmentation:
### Step 1: TypeScript Declaration
Create a declaration file (e.g., `gecko.d.ts`) in your project:
```typescript
import "@geckoui/geckoui";
declare module "@geckoui/geckoui" {
interface SwitchSizeMap {
lg: unknown;
}
}
```
### Step 2: Add CSS Styles
Add the corresponding styles to your global CSS/SCSS file. You need to define both the container size and the thumb element:
```scss
.GeckoUISwitch {
&[data-size="lg"] {
@apply h-8 w-14;
}
&__thumb {
&[data-size="lg"] {
@apply w-6 translate-x-0.5 group-data-[checked]:translate-x-3.5;
}
}
}
```
### Step 3: Use Your Custom Size
```tsx
```
## Accessibility
* Built on HeadlessUI for WCAG compliance
* Proper ARIA attributes automatically applied
* Fully keyboard navigable
* Focus visible for keyboard users
* Works with screen readers
* Disabled state properly communicated
## Styling
The component uses class names with data attributes:
* `GeckoUISwitch` - Main container
* `GeckoUISwitch[data-size="sm|md"]` - Size modifiers
* `GeckoUISwitch__thumb` - Toggle thumb element
* `GeckoUISwitch__thumb[data-size="sm|md"]` - Thumb size modifiers
The switch also supports HeadlessUI data attributes:
* `data-[checked]` - Applied when checked
* `data-[disabled]` - Applied when disabled
## Dependencies
This component uses `@headlessui/react` for accessibility and behavior.
## Related Components
* [Checkbox](/docs/checkbox) - For multi-select options
* [Radio](/docs/radio) - For mutually exclusive selections
* [RHFSwitch](/docs/rhf-switch) - React Hook Form integration
# Textarea
import { Textarea } from '@geckoui/geckoui';
# Textarea
A textarea component built on react-textarea-autosize that can automatically adjust its height based on content. Control the minimum and maximum number of rows displayed.
## Installation
```tsx
import { Textarea } from '@geckoui/geckoui';
```
## Basic Usage
```tsx
```
## Props API
| Prop | Type | Default | Description |
| ------------ | ------------------------ | ------- | ----------------------------------------------------- |
| `autoResize` | `boolean` | `false` | Enable automatic height adjustment based on content |
| `rows` | `number` | `2` | Minimum number of rows to display |
| `maxRows` | `number` | - | Maximum number of rows (only works with `autoResize`) |
| `className` | `string` | - | CSS class for styling |
| `disabled` | `boolean` | `false` | Disable the textarea |
| `readOnly` | `boolean` | `false` | Make the textarea read-only |
| ...rest | `TextareaHTMLAttributes` | - | All standard HTML textarea attributes |
## Examples
### Auto-Resizing Textarea
```tsx
```
### With Row Constraints
```tsx
```
### Auto-Resize with Max Rows
```tsx
```
### Disabled State
```tsx
```
### Read-Only State
```tsx
```
## Auto-Resize Behavior
When `autoResize` is enabled:
* The textarea grows automatically as content increases
* Minimum height is determined by the `rows` prop
* Maximum height is determined by the `maxRows` prop
* Shrinks back down when content is removed
When `autoResize` is disabled:
* The textarea has a fixed height
* Users can resize manually (unless CSS prevents it)
* The `maxRows` prop is ignored
## Accessibility
* Fully keyboard navigable
* Works with screen readers
* Supports all ARIA textarea attributes
* Disabled state is properly communicated
## Styling
The component uses the class name `GeckoUITextarea`. Override styles using the `className` prop:
```css
.GeckoUITextarea {
/* Your custom styles */
}
```
## Dependencies
This component uses `react-textarea-autosize` for the auto-resize functionality.
## Related Components
* [Input](/docs/input) - Single-line text input
* [Label](/docs/label) - Form labels
* [InputError](/docs/input-error) - Error messages
* [RHFTextarea](/docs/rhf-textarea) - React Hook Form integration
# Theming
# Theming
Gecko UI uses CSS custom properties (variables) for theming with [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) color values. You can override theme colors using any valid CSS color format â `oklch()`, `hex`, `rgb()`, `hsl()`, or any other format you prefer.
## CSS Variables
Theme colors are defined as `--color-*` CSS custom properties. These are standard [Tailwind CSS v4 theme variables](https://tailwindcss.com/docs/theme) and work seamlessly with Tailwind's opacity modifier syntax (e.g., `bg-primary-600/50`).
### Primary Colors
Primary color scale from 50 (lightest) to 950 (darkest):
```css
:root {
--color-primary-50: oklch(0.9705 0.0142 254.6);
--color-primary-100: oklch(0.9319 0.0316 255.59);
--color-primary-200: oklch(0.8823 0.0571 254.13);
--color-primary-300: oklch(0.8091 0.0956 251.81);
--color-primary-400: oklch(0.7137 0.1434 254.62);
--color-primary-500: oklch(0.6231 0.188 259.81);
--color-primary-600: oklch(0.5461 0.2152 262.88);
--color-primary-700: oklch(0.4882 0.2172 264.38);
--color-primary-800: oklch(0.4244 0.1809 265.64);
--color-primary-900: oklch(0.3791 0.1378 265.52);
--color-primary-950: oklch(0.2823 0.0874 267.94);
}
```
### Surface Colors
Background colors for various UI surfaces:
```css
:root {
--color-surface-primary: oklch(1 0 none); /* Main background */
--color-surface-secondary: oklch(0.9851 0 none); /* Secondary background */
--color-surface-tertiary: oklch(0.9702 0 none); /* Tertiary background */
--color-surface-hover: oklch(0.9702 0 none); /* Hover state */
--color-surface-hover-strong: oklch(0.9401 0 none); /* Strong hover state */
--color-surface-active: oklch(0.8699 0 none); /* Active/pressed state */
--color-surface-disabled: oklch(0.9401 0 none); /* Disabled state */
--color-surface-overlay: oklch(0 0 none); /* Backdrop overlay */
--color-surface-autofill: oklch(0.9702 0 none); /* Browser autofill */
--color-surface-emphasis: oklch(0.4461 0.0263 256.8);
}
```
### Text Colors
Text colors for different hierarchy levels:
```css
:root {
--color-text-primary: oklch(0.2046 0 none); /* Primary text */
--color-text-secondary: oklch(0.3715 0 none); /* Secondary text */
--color-text-tertiary: oklch(0.5555 0 none); /* Tertiary text */
--color-text-disabled: oklch(0.7155 0 none); /* Disabled text */
--color-text-placeholder: oklch(0.7155 0 none); /* Placeholder text */
--color-text-inverse: oklch(0.9851 0 none); /* Inverse text (on dark) */
--color-text-muted: oklch(0.5555 0 none); /* Muted text */
--color-text-on-primary: oklch(1 0 none); /* Text on primary color */
}
```
### Border Colors
Border colors for various states:
```css
:root {
--color-border-primary: oklch(0.9401 0 none); /* Default border */
--color-border-secondary: oklch(0.8699 0 none); /* Secondary border */
--color-border-focus: oklch(0.7155 0 none); /* Focus state */
--color-border-hover: oklch(0.8699 0 none); /* Hover state */
--color-border-disabled: oklch(0.9401 0 none); /* Disabled state */
}
```
### Scrollbar Colors
Custom scrollbar styling (these use the `--gecko-ui-` prefix as they are not Tailwind utilities):
```css
:root {
--gecko-ui-scrollbar-track: oklch(0 0 none);
--gecko-ui-scrollbar-thumb: oklch(0.8717 0.0093 258.34);
--gecko-ui-scrollbar-thumb-hover: oklch(0.7137 0.0192 261.32);
}
```
## Dark Mode
Gecko UI includes built-in dark mode support. Apply the `.dark` class to enable dark theme:
```css
.dark {
--color-surface-primary: oklch(0.2046 0 none);
--color-surface-secondary: oklch(0.2435 0 none);
--color-surface-tertiary: oklch(0.2972 0 none);
--color-surface-hover: oklch(0.2972 0 none);
--color-surface-hover-strong: oklch(0.3715 0 none);
--color-surface-active: oklch(0.5555 0 none);
--color-surface-disabled: oklch(0.2972 0 none);
--color-surface-overlay: oklch(0 0 none);
--color-surface-autofill: oklch(0.2435 0 none);
--color-surface-emphasis: oklch(0.7748 0.0054 247.89);
--color-text-primary: oklch(0.9851 0 none);
--color-text-secondary: oklch(0.8699 0 none);
--color-text-tertiary: oklch(0.7155 0 none);
--color-text-disabled: oklch(0.5555 0 none);
--color-text-placeholder: oklch(0.5555 0 none);
--color-text-inverse: oklch(0.2046 0 none);
--color-text-muted: oklch(0.7155 0 none);
--color-border-primary: oklch(0.4676 0 none);
--color-border-secondary: oklch(0.4676 0 none);
--color-border-focus: oklch(0.5555 0 none);
--color-border-hover: oklch(0.7155 0 none);
--color-border-disabled: oklch(0.2972 0 none);
--gecko-ui-scrollbar-track: oklch(0 0 none);
--gecko-ui-scrollbar-thumb: oklch(0.4461 0.0263 256.8);
--gecko-ui-scrollbar-thumb-hover: oklch(0.551 0.0234 264.36);
}
```
### Implementing Dark Mode
Add the `dark` class to your root element:
```tsx
import { useState } from 'react';
function App() {
const [isDark, setIsDark] = useState(false);
return (
setIsDark(!isDark)}>
Toggle Dark Mode
{/* Your app content */}
);
}
```
Or apply it to the `` element:
```tsx
'use client';
import { useEffect, useState } from 'react';
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
useEffect(() {
document.documentElement.classList.toggle('dark', theme === 'dark');
}, [theme]);
return <>{children}>;
}
```
## Customizing Colors
### Changing Primary Color
Override the primary color scale to match your brand. You can use **any CSS color format**:
```css
:root {
/* Using oklch */
--color-primary-50: oklch(0.98 0.02 330);
--color-primary-100: oklch(0.95 0.04 330);
--color-primary-200: oklch(0.90 0.08 330);
--color-primary-300: oklch(0.82 0.14 330);
--color-primary-400: oklch(0.72 0.19 330);
--color-primary-500: oklch(0.65 0.24 330);
--color-primary-600: oklch(0.55 0.22 330);
--color-primary-700: oklch(0.48 0.19 330);
--color-primary-800: oklch(0.40 0.16 330);
--color-primary-900: oklch(0.35 0.12 330);
--color-primary-950: oklch(0.25 0.08 330);
}
```
```css
:root {
/* Using hex â works too! */
--color-primary-500: #8b5cf6;
--color-primary-600: #7c3aed;
--color-primary-700: #6d28d9;
}
```
### Customizing Specific Colors
Override individual tokens:
```css
:root {
/* Make borders more prominent */
--color-border-primary: oklch(0.8 0 none);
/* Adjust text hierarchy */
--color-text-secondary: oklch(0.4 0 none);
/* Custom hover states */
--color-surface-hover: oklch(0.95 0 none);
}
```
## Using Color Variables in Custom CSS
Since variables contain full color values, use them directly with `var()`:
```css
/* Direct usage */
color: var(--color-text-primary);
background: var(--color-surface-primary);
/* With opacity using color-mix() */
color: color-mix(in oklch, var(--color-text-primary) 50%, transparent);
background: color-mix(in oklch, var(--color-surface-primary) 80%, transparent);
/* In Tailwind arbitrary values */
className="text-[var(--color-text-primary)]"
className="bg-[var(--color-surface-hover)]"
```
## Component-Specific Styling
Components use their own class names for targeted styling:
```css
/* Style all inputs */
.GeckoUIInput {
--color-border-primary: oklch(0.75 0 none);
}
/* Style all buttons */
.GeckoUIButton {
border-radius: 0.5rem;
}
/* Style specific button variant */
.GeckoUIButton[data-variant="filled"] {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
```
## Customizing Icons
Gecko UI uses CSS `mask-image` with inline SVGs for icons. This approach allows icons to inherit color from parent elements and be styled with CSS.
To find an icon's class name, use your browser's DevTools to inspect the element. All icon classes follow a consistent naming pattern (e.g., `.GeckoUI-icon__check`, `.GeckoUIAlert__icon[data-variant="error"]`).
### Overriding an Icon
To replace an icon, override the `mask-image` property with your custom SVG:
```css
.GeckoUI-icon__check {
mask-image: url('data:image/svg+xml, ');
-webkit-mask-image: url('data:image/svg+xml, ');
}
```
### Overriding Alert Icons
Alert icons are variant-specific. Override them by targeting the variant modifier:
```css
.GeckoUIAlert__icon[data-variant="error"] {
@apply bg-red-600;
mask-image: url('data:image/svg+xml, ');
-webkit-mask-image: url('data:image/svg+xml, ');
}
```
### Icon Styling Properties
Icons use these CSS properties which you can also customize:
```css
.GeckoUI-icon__check {
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;
-webkit-mask-position: center;
-webkit-mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
```
### Using External Icon Libraries
You can also use icons from external libraries by converting them to data URLs:
```css
/* Using a Lucide icon */
.GeckoUI-icon__arrow-down {
mask-image: url('data:image/svg+xml, ');
-webkit-mask-image: url('data:image/svg+xml, ');
}
```
## Best Practices
1. **Use Any Color Format**: Override with `oklch()`, `hex`, `rgb()`, `hsl()` â whatever you prefer
2. **Maintain Consistency**: Keep color scales consistent with 50-950 range
3. **Test Contrast**: Ensure text colors meet WCAG contrast requirements
4. **Dark Mode Parity**: When customizing light mode, also update dark mode tokens
5. **Component Overrides**: Use component-specific classes (`.GeckoUIButton`) rather than global tokens when possible
## Example: Complete Custom Theme
```css
/* custom-theme.css */
:root {
/* Brand primary color (Purple) */
--color-primary-50: oklch(0.98 0.02 300);
--color-primary-100: oklch(0.95 0.05 300);
--color-primary-200: oklch(0.90 0.09 300);
--color-primary-300: oklch(0.84 0.14 300);
--color-primary-400: oklch(0.74 0.19 300);
--color-primary-500: oklch(0.65 0.24 300);
--color-primary-600: oklch(0.56 0.23 300);
--color-primary-700: oklch(0.49 0.20 300);
--color-primary-800: oklch(0.42 0.17 300);
--color-primary-900: oklch(0.36 0.13 300);
--color-primary-950: oklch(0.26 0.08 300);
}
```
Import after the base styles:
```tsx
import '@geckoui/geckoui/styles.css';
import './custom-theme.css';
```
## Next Steps
* [Installation](/docs/installation) - Setup guide
# Toast
import { toast } from '@geckoui/geckoui';
import { BasicToastExample, ToastVariantsExample, ToastWithDescriptionExample, ToastWithActionExample, PromiseToastExample, CustomDurationExample, LoadingToastExample, DismissToastExample, CustomStyledToastExample, RichContentToastExample, PositionExample } from '@/components/examples/toast-examples';
# Toast
Display brief, non-intrusive notification messages to provide feedback on user actions. Built on [Sonner](https://sonner.emilkowal.ski/), featuring automatic stacking, promise handling, and rich customization options.
## Installation
```tsx
import { toast, GeckoUIPortal } from '@geckoui/geckoui';
```
## Setup
Add `GeckoUIPortal` to your app root to enable toast notifications:
```tsx
// app/layout.tsx or _app.tsx
import { GeckoUIPortal } from '@geckoui/geckoui';
export default function RootLayout({ children }) {
return (
{children}
);
}
```
## Basic Usage
```tsx
'use client';
import { toast, Button } from '@geckoui/geckoui';
function Example() {
return (
toast('This is a basic toast notification')}>
Show Toast
);
}
```
## Toast API
### `toast(message, options?)`
Display a default toast notification.
```tsx
toast('Hello World');
toast('Hello World', { duration: 5000 });
```
### Variant Methods
| Method | Description |
| ---------------------------------- | -------------------------------- |
| `toast.success(message, options?)` | Success notification (green) |
| `toast.error(message, options?)` | Error notification (red) |
| `toast.warning(message, options?)` | Warning notification (yellow) |
| `toast.info(message, options?)` | Info notification (blue) |
| `toast.loading(message, options?)` | Loading state with spinner |
| `toast.promise(promise, messages)` | Automatic promise state handling |
### Control Methods
| Method | Description |
| ----------------------------------- | -------------------------------------- |
| `toast.dismiss(id?)` | Dismiss a specific toast or all toasts |
| `toast.custom(component, options?)` | Display a custom component |
## Options
| Option | Type | Default | Description |
| ------------- | ------------------------------------------------------------------------------------------------- | ---------------- | ---------------------------------------------- |
| `description` | `string \| ReactNode` | - | Additional description below the message |
| `duration` | `number` | `4000` | Duration in ms (use `Infinity` for persistent) |
| `position` | `'top-left' \| 'top-center' \| 'top-right' \| 'bottom-left' \| 'bottom-center' \| 'bottom-right'` | `'bottom-right'` | Toast position on screen |
| `action` | `{ label: string; onClick: () => void }` | - | Action button |
| `cancel` | `{ label: string; onClick: () => void }` | - | Cancel button |
| `id` | `string \| number` | - | Custom toast ID |
| `onDismiss` | `() => void` | - | Callback when dismissed |
| `onAutoClose` | `() => void` | - | Callback when auto-closed |
| `className` | `string` | - | Custom CSS class |
| `style` | `CSSProperties` | - | Inline styles |
| `closeButton` | `boolean` | `false` | Show close button |
## Examples
### Toast Variants
```tsx
toast.success('Operation completed successfully!')}>
Success
toast.error('An error occurred!')}>
Error
toast.warning('This is a warning message')}>
Warning
toast.info('Here is some information')}>
Info
```
### With Description
```tsx
toast.success('Account created', {
description: 'Your account has been created successfully. You can now sign in.'
});
toast.error('Upload failed', {
description: 'The file size exceeds the 10MB limit. Please choose a smaller file.'
});
```
### With Action Button
```tsx
toast('Email sent', {
description: 'Your email has been sent to john@example.com',
action: {
label: 'Undo',
onClick: () => toast.info('Email sending cancelled')
}
});
```
### Promise Toast
Automatically handles loading, success, and error states:
```tsx
'use client';
import { toast, Button } from '@geckoui/geckoui';
function Example() {
const downloadReport = async () => {
const response = await fetch('/api/report');
if (!response.ok) throw new Error('Download failed');
return response.json();
};
return (
toast.promise(downloadReport(), {
loading: 'Downloading report...',
success: (data) => `${data.name} downloaded successfully`,
error: 'Download failed. Please try again.'
})
}
>
Download Report
);
}
```
### Custom Duration
```tsx
// Quick message (1 second)
toast('Quick message', { duration: 1000 });
// Normal message (4 seconds - default)
toast('Normal message', { duration: 4000 });
// Persistent message (stays until dismissed)
toast('Persistent message', { duration: Infinity });
```
### Loading Toast
```tsx
function Example() {
const handleProcess = () => {
const toastId = toast.loading('Processing your request...');
// Simulate async operation
setTimeout(() => {
toast.success('Process completed!', { id: toastId });
}, 3000);
};
return Start Process ;
}
```
### Dismiss Toasts
```tsx
// Dismiss a specific toast
const id = toast('This toast can be dismissed manually');
toast.dismiss(id);
// Dismiss all toasts
toast.dismiss();
// Auto-dismiss after delay
const toastId = toast('Auto-dismissing...', { duration: Infinity });
setTimeout(() => toast.dismiss(toastId), 3000);
```
### Custom Styling
```tsx
toast('Custom styled toast', {
className: 'border-2 border-blue-500',
style: {
background: 'var(--color-surface-secondary)'
}
});
```
### Rich Content
```tsx
toast(
New Feature Available
Check out our new dashboard analytics with real-time data visualization.
,
{ duration: 5000 }
);
```
### Toast Positions
```tsx
toast('Top Left', { position: 'top-left' });
toast('Top Center', { position: 'top-center' });
toast('Top Right', { position: 'top-right' });
toast('Bottom Left', { position: 'bottom-left' });
toast('Bottom Center', { position: 'bottom-center' });
toast('Bottom Right', { position: 'bottom-right' });
```
## Global Configuration
Configure toast defaults in `GeckoUIPortal`:
```tsx
```
### Available Global Options
| Option | Type | Default | Description |
| --------------- | ------------------------------- | ---------------- | ------------------------------- |
| `position` | `Position` | `'bottom-right'` | Default position |
| `duration` | `number` | `4000` | Default duration |
| `closeButton` | `boolean` | `false` | Show close button on all toasts |
| `richColors` | `boolean` | `false` | Enhanced colors for variants |
| `expand` | `boolean` | `false` | Expand toasts on hover |
| `visibleToasts` | `number` | `3` | Max visible toasts |
| `gap` | `number` | `14` | Gap between toasts |
| `offset` | `string` | - | Offset from edge |
| `theme` | `'light' \| 'dark' \| 'system'` | `'light'` | Color theme |
## Accessibility
* Toasts are announced by screen readers
* Automatically removed after duration expires
* Action buttons are keyboard accessible
* Supports `prefers-reduced-motion` for animations
* ARIA live regions for dynamic content
## Best Practices
1. **Keep messages brief**: Toast messages should be short and scannable
2. **Use appropriate variants**: Match the toast type to the message (success, error, etc.)
3. **Provide actions when needed**: Allow users to undo or retry failed operations
4. **Don't overuse**: Avoid showing too many toasts at once
5. **Set appropriate durations**:
* Success: 2-4 seconds
* Error: 5-7 seconds (or with action)
* Loading: Until operation completes
6. **Position wisely**: Bottom-right is least intrusive for most layouts
7. **Avoid critical information**: Don't use toasts for important information that users must read
## Related Components
* [Alert](/docs/alert) - Display prominent alerts in content
* [Dialog](/docs/dialog) - Modal dialogs for important messages
## Resources
* [Sonner Documentation](https://sonner.emilkowal.ski/)
# Tooltip
import { Tooltip, Button } from '@geckoui/geckoui';
# Tooltip
Tooltip displays contextual information in a floating overlay when users hover over or focus on an element. Built on Floating UI, it provides accessible tooltips with customizable positioning, delays, and styling.
## Installation
```tsx
import { Tooltip } from '@geckoui/geckoui';
```
## Basic Usage
Hover me
```tsx
Hover me
```
## Props API
| Prop | Type | Default | Description |
| ------------------ | ---------------------------------------- | ------------------------------- | -------------------------- |
| `content` | `string \| ReactNode \| FC` | - | Tooltip content |
| `side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'top'` | Tooltip position |
| `delayDuration` | `number` | `700` | Delay before showing (ms) |
| `sideOffset` | `number` | `5` | Distance from trigger (px) |
| `backgroundColor` | `string` | CSS variable `surface-emphasis` | Tooltip background color |
| `className` | `string` | - | CSS class for tooltip |
| `triggerClassName` | `string` | - | CSS class for trigger |
| `triggerAsChild` | `boolean` | `false` | Merge trigger with child |
| `arrowClassName` | `string` | - | CSS class for arrow |
| `children` | `ReactNode` | - | Trigger element (required) |
## Examples
### Different Positions
Top
Right
Bottom
Left
```tsx
Top
Right
Bottom
Left
```
### Icon Buttons
â
âī¸
đī¸
```tsx
```
### Complex Content
Premium Feature
Upgrade to access this feature