Reference for all components, handlers, and utilities in @goobits/ui.
Quick Links: Form Components | UI Components | Handlers | Configuration | Utilities | Services
| Component | Use When | Category Selector | Bundle Size | Best For |
|---|---|---|---|---|
| ContactForm | Single form type | No | +12KB | Simple contact page, predetermined category |
| CategoryContactForm | 2+ form types | Yes (dropdown) | +15KB | Multi-department routing, consolidated forms |
| ContactFormPage | Need full page layout | Configurable | +18KB | Complete form page with header/footer |
| FeedbackForm | Quick feedback widget | No | +8KB | Embedded widget, minimal fields |
:::tip Decision Guide: Choosing a Form Component
-
Use ContactForm when:
- You have ONE type of inquiry (e.g., "Contact Us" page)
- Category is predetermined programmatically
- You want simpler UI without category selector
-
Use CategoryContactForm when:
- You have 2+ inquiry types (Support, Sales, General)
- Users need to select their category
- Different fields required per category
-
Use ContactFormPage when:
- You want complete page layout with consistent styling
- Need header/footer around the form
-
Use FeedbackForm when:
- Embedded widget (not full page)
- Quick feedback only (name, email, message)
- Minimal UI footprint :::
See how form components connect to configuration, API handlers, and services:
graph TB
subgraph "Client Side (Browser)"
CF[ContactForm<br/>Single category]
CCF[CategoryContactForm<br/>Multi-category selector]
CFP[ContactFormPage<br/>Full page layout]
FF[FeedbackForm<br/>Minimal widget]
end
subgraph "Configuration Layer"
Config[contactConfig<br/>categories, fields, security]
Init[initContactFormConfig<br/>in hooks.server.js]
end
subgraph "Server Side APIs"
CSRF[/api/csrf<br/>CSRF token generation]
Handler[/api/contact<br/>createContactApiHandler]
end
subgraph "Backend Services"
Validation[Form Validation<br/>Zod schemas]
RateLimit[Rate Limiter<br/>IP-based throttling]
reCAPTCHA[reCAPTCHA Service<br/>Bot detection]
Email[Email Service<br/>Mock/Nodemailer/AWS SES]
end
CF --> Config
CCF --> Config
CFP --> Config
FF --> Config
Config --> Init
CF --> CSRF
CCF --> CSRF
CFP --> CSRF
CF --> Handler
CCF --> Handler
CFP --> Handler
FF --> Handler
Handler --> Validation
Handler --> RateLimit
Handler --> reCAPTCHA
Handler --> Email
style CF fill:#3b82f6,color:#fff
style CCF fill:#3b82f6,color:#fff
style CFP fill:#3b82f6,color:#fff
style FF fill:#3b82f6,color:#fff
style Handler fill:#10b981,color:#fff
style Email fill:#f59e0b,color:#fff
Main form component with validation, CSRF protection, and category-based field rendering.
Import:
import { ContactForm } from '@goobits/ui/ui';Props:
| Prop | Type | Default | Description |
|---|---|---|---|
apiEndpoint |
string |
'/api/contact' |
API endpoint for form submission |
csrfToken |
string |
'' |
CSRF token for security (optional, auto-fetched if not provided). Performance: Pre-fetch in +page.server.js to avoid 100-200ms delay on submission |
initialData |
object |
{} |
Pre-populate form fields |
messages |
object |
{} |
Override default i18n messages |
submitContactForm |
function |
Built-in handler | Custom form submission function |
Usage:
<script>
import { ContactForm } from '@goobits/ui/ui';
</script>
<ContactForm
apiEndpoint="/api/contact"
initialData={{ name: 'John', email: 'john@example.com' }}
/>Features:
- Category-based field rendering
- Auto-save to localStorage
- Form hydration on page load
- reCAPTCHA integration
- CSRF token handling
- Screen reader announcements
- Validation with Zod schemas
Form with category selection dropdown for multiple form types.
Import:
import { CategoryContactForm } from '@goobits/ui/ui';Props:
| Prop | Type | Default | Description |
|---|---|---|---|
apiEndpoint |
string |
'/api/contact' |
API endpoint for form submission |
csrfToken |
string |
'' |
CSRF token for security |
initialData |
object |
{} |
Pre-populate form fields |
messages |
object |
{} |
Override i18n messages |
showCategorySelector |
boolean |
true |
Show/hide category dropdown |
Usage:
<script>
import { CategoryContactForm } from '@goobits/ui/ui';
</script>
<CategoryContactForm
apiEndpoint="/api/contact"
showCategorySelector={true}
/>When to use: Use CategoryContactForm when users need to select from multiple form types (general inquiry, support, feedback). Use ContactForm for single-purpose forms.
Feedback widget for collecting user input.
Import:
import { FeedbackForm } from '@goobits/ui/ui';Props:
| Prop | Type | Default | Description |
|---|---|---|---|
feedbackType |
string |
'' |
Pre-selected feedback type |
userComment |
string |
'' |
Pre-filled comment |
userName |
string |
'' |
Pre-filled user name |
userEmail |
string |
'' |
Pre-filled user email |
messages |
object |
{} |
Override i18n messages |
isFormVisible |
boolean |
false |
Initial form visibility |
isThankYouVisible |
boolean |
false |
Initial thank you visibility |
submitContactForm |
function |
Built-in handler | Custom submission function |
Usage:
<script>
import { FeedbackForm } from '@goobits/ui/ui';
</script>
<FeedbackForm
isFormVisible={true}
messages={{ feedbackPlaceholder: 'Tell us what you think...' }}
/>Page layout with form, header, and footer.
Import:
import { ContactFormPage } from '@goobits/ui/ui';Props: Same as ContactForm plus additional layout props.
Usage:
<ContactFormPage apiEndpoint="/api/contact" />Reusable field component for custom forms.
Import:
import { FormField } from '@goobits/ui/ui';Props:
| Prop | Type | Required | Description |
|---|---|---|---|
field |
object |
Yes | Field configuration object |
value |
any |
Yes | Current field value |
errors |
array |
No | Validation errors |
touched |
boolean |
No | Whether field has been touched |
onChange |
function |
Yes | Value change handler |
onBlur |
function |
Yes | Blur event handler |
Image upload component with preview and validation.
Import:
import { UploadImage } from '@goobits/ui/ui';Props:
| Prop | Type | Default | Description |
|---|---|---|---|
value |
File | null |
null |
Current uploaded file |
maxSize |
number |
5242880 |
Max file size in bytes (default 5MB) |
acceptedTypes |
array |
['image/jpeg', 'image/png', ...] |
Allowed MIME types |
onChange |
function |
Required | File change handler |
error |
string |
'' |
Validation error message |
Usage:
<script>
import { UploadImage } from '@goobits/ui/ui';
let file = $state(null);
</script>
<UploadImage
value={file}
maxSize={10485760}
onChange={(newFile) => file = newFile}
/>Text input component with validation styling.
Import:
import { Input } from '@goobits/ui/ui';Props:
| Prop | Type | Default | Description |
|---|---|---|---|
type |
string |
'text' |
Input type (text, email, tel, etc.) |
value |
string |
'' |
Current value |
placeholder |
string |
'' |
Placeholder text |
error |
string |
'' |
Error message |
disabled |
boolean |
false |
Disabled state |
required |
boolean |
false |
Required field |
Multi-line text input component.
Import:
import { Textarea } from '@goobits/ui/ui';Props: Same as Input plus:
| Prop | Type | Default | Description |
|---|---|---|---|
rows |
number |
4 |
Number of visible rows |
maxlength |
number |
undefined |
Maximum character length |
Dropdown select component.
Import:
import { SelectMenu } from '@goobits/ui/ui';Props:
| Prop | Type | Default | Description |
|---|---|---|---|
options |
array |
[] |
Array of {value, label} objects |
value |
string |
'' |
Selected value |
placeholder |
string |
'Select...' |
Placeholder text |
onChange |
function |
Required | Selection change handler |
Toggle switch component for boolean values.
Import:
import { ToggleSwitch } from '@goobits/ui/ui';Props:
| Prop | Type | Default | Description |
|---|---|---|---|
checked |
boolean |
false |
Toggle state |
label |
string |
'' |
Label text |
onChange |
function |
Required | State change handler |
disabled |
boolean |
false |
Disabled state |
Import:
import { Modal, Alert, Confirm, AppleModal } from '@goobits/ui/ui/modals';Base modal component.
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
isOpen |
boolean |
false |
Modal visibility |
title |
string |
'' |
Modal title |
onClose |
function |
Required | Close handler |
size |
string |
'medium' |
Size (small, medium, large) |
Alert dialog for notifications.
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
isOpen |
boolean |
false |
Alert visibility |
title |
string |
'' |
Alert title |
message |
string |
'' |
Alert message |
type |
string |
'info' |
Type (info, success, warning, error) |
onClose |
function |
Required | Close handler |
Confirmation dialog with actions.
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
isOpen |
boolean |
false |
Confirm visibility |
title |
string |
'' |
Confirm title |
message |
string |
'' |
Confirm message |
confirmText |
string |
'Confirm' |
Confirm button text |
cancelText |
string |
'Cancel' |
Cancel button text |
onConfirm |
function |
Required | Confirm action handler |
onCancel |
function |
Required | Cancel action handler |
Apple-style modal with animations.
Props: Same as Modal with additional animation options.
Import:
import { Menu, ContextMenu, MenuItem, MenuSeparator } from '@goobits/ui/ui';Dropdown menu component.
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
items |
array |
[] |
Menu items configuration |
isVisible |
boolean |
false |
Menu visibility |
x |
number |
0 |
X position |
y |
number |
0 |
Y position |
onClose |
function |
Required | Close handler |
placement |
string |
'bottom-start' |
Menu placement |
Item Configuration:
const items = [
{ type: 'action', label: 'Edit', onClick: () => {} },
{ type: 'separator' },
{ type: 'destructive', label: 'Delete', onClick: () => {} }
];Context menu with right-click trigger.
Props: Same as Menu plus:
| Prop | Type | Default | Description |
|---|---|---|---|
target |
HTMLElement |
Required | Target element for context menu |
Import:
import { tooltip, TooltipPortal } from '@goobits/ui/ui/tooltip';Use as Svelte action for hover tooltips.
Usage:
<script>
import { tooltip, TooltipPortal } from '@goobits/ui/ui/tooltip';
</script>
<!-- Add portal to layout -->
<TooltipPortal />
<!-- Use tooltip action -->
<button use:tooltip={{ content: "Click me" }}>
Hover for tooltip
</button>
<!-- Click mode -->
<button use:tooltip={{ content: "Info", showOnClick: true }}>
Click for tooltip
</button>Options:
| Option | Type | Default | Description |
|---|---|---|---|
content |
string | function |
Required | Tooltip content |
showOnClick |
boolean |
false |
Show on click instead of hover |
placement |
string |
'top' |
Placement (top, bottom, left, right) |
delay |
number |
200 |
Show delay in ms |
hideDelay |
number |
0 |
Hide delay in ms |
Creates a SvelteKit API route handler for contact forms.
Import:
import { createContactApiHandler } from '@goobits/ui/handlers/contactFormHandler';Options:
| Option | Type | Default | Description |
|---|---|---|---|
adminEmail |
string |
Required | Admin notification email |
fromEmail |
string |
Required | From email address |
emailServiceConfig |
object |
{} |
Email service configuration |
recaptchaSecretKey |
string |
'' |
reCAPTCHA secret key |
recaptchaMinScore |
number |
0.5 |
Minimum reCAPTCHA score |
rateLimitMaxRequests |
number |
3 |
Max requests per window |
rateLimitWindowMs |
number |
3600000 |
Rate limit window (ms, default 1 hour) |
successMessage |
string |
Default message | Custom success message |
errorMessage |
string |
Default message | Custom error message |
customValidation |
function |
null |
Custom validation logic |
customSuccessHandler |
function |
null |
Custom success handler |
Usage:
// /api/contact/+server.js
import { createContactApiHandler } from '@goobits/ui/handlers/contactFormHandler';
export const POST = createContactApiHandler({
adminEmail: process.env.ADMIN_EMAIL,
fromEmail: process.env.FROM_EMAIL,
recaptchaSecretKey: process.env.RECAPTCHA_SECRET_KEY,
customSuccessHandler: async (data) => {
// Store in database
await db.insert(data);
return { message: 'Success!', id: data.id };
}
});Initialize form configuration with categories and fields.
Import:
import { initContactFormConfig } from '@goobits/ui/config';Usage:
// src/hooks.server.js or src/app.js
import { initContactFormConfig } from '@goobits/ui/config';
const contactConfig = {
appName: 'My App',
categories: {
general: {
label: 'General Inquiry',
fields: ['name', 'email', 'message']
},
support: {
label: 'Technical Support',
fields: ['name', 'email', 'phone', 'message', 'attachments']
}
},
recaptcha: {
enabled: true,
provider: 'google-v3',
siteKey: 'YOUR_SITE_KEY',
minScore: 0.5
},
fileSettings: {
maxFileSize: 5 * 1024 * 1024, // 5MB
acceptedImageTypes: ['image/jpeg', 'image/png', 'image/webp']
}
};
initContactFormConfig(contactConfig);Configuration Schema:
interface ContactConfig {
appName: string;
categories: Record<string, CategoryConfig>;
recaptcha?: RecaptchaConfig;
fileSettings?: FileSettings;
ui?: UIConfig;
}
interface CategoryConfig {
label: string;
icon?: string;
fields: string[];
}
interface RecaptchaConfig {
enabled: boolean;
provider: 'google-v3' | 'hcaptcha' | 'turnstile';
siteKey: string;
minScore?: number; // 0.0 - 1.0 for v3
}
interface FileSettings {
maxFileSize: number; // in bytes
acceptedImageTypes: string[]; // MIME types
}Import:
import {
generateCsrfToken,
validateCsrfToken,
createCsrfProtection
} from '@goobits/ui/security/csrf';Usage:
// Generate token
const token = generateCsrfToken();
// Validate token
const isValid = validateCsrfToken(token, storedToken);
// SvelteKit middleware
export const handle = createCsrfProtection();Import:
import {
handleFormI18n,
loadWithFormI18n,
createMessageGetter
} from '@goobits/ui/i18n';Usage:
// hooks.server.js - Auto-detection
export async function handle({ event, resolve }) {
await handleFormI18n(event);
return await resolve(event);
}
// +page.server.js - Data loading
export const load = async (event) => {
return await loadWithFormI18n(event, async () => {
return { /* your data */ };
});
};
// Component - Message override
import { createMessageGetter } from '@goobits/ui/i18n';
const getMessage = createMessageGetter(customMessages);Import:
import { contactSchema, feedbackSchema } from '@goobits/ui/validation';Usage:
import { z } from 'zod';
import { contactSchema } from '@goobits/ui/validation';
// Extend existing schema
const customSchema = contactSchema.extend({
company: z.string().optional()
});
// Server-side validation
const result = customSchema.safeParse(formData);
if (!result.success) {
return { errors: result.error.flatten() };
}Advanced services for form handling, state management, and integrations.
Persist form data to localStorage to prevent data loss.
Import:
import {
saveFormData,
loadFormData,
clearFormData,
hasSavedData
} from '@goobits/ui/services';Usage:
// Save form data
saveFormData({ name: 'John', email: 'john@example.com' }, 'contact');
// Load saved data
const data = loadFormData('contact');
// Clear data
clearFormData('contact');Pre-fill forms with saved or test data.
Import:
import { hydrateForm, getTestDataForCategory } from '@goobits/ui/services';Usage:
// Hydrate form with saved data
const formData = hydrateForm({ category: 'contact', useTestData: false });
// Get test data for development
const testData = getTestDataForCategory('support');Accessibility announcements for screen readers.
Import:
import {
announce,
announceFormErrors,
announceFormStatus
} from '@goobits/ui/services';Usage:
// Announce message
announce('Form submitted successfully', { priority: 'polite' });
// Announce errors
announceFormErrors({ email: ['Invalid email'], name: ['Required'] });
// Announce status
announceFormStatus('submitting');Email delivery via Nodemailer, AWS SES, or mock provider.
Import:
import { createEmailProvider } from '@goobits/ui/services';Usage:
// Create provider
const emailService = createEmailProvider({
provider: 'nodemailer',
smtp: {
host: 'smtp.gmail.com',
port: 587,
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS }
}
});
// Send email
await emailService.sendEmail({
to: 'admin@example.com',
subject: 'New Contact',
text: 'Message from user',
html: '<p>Message from user</p>'
});IP and email-based rate limiting.
Import:
import { rateLimitFormSubmission, resetIpRateLimit } from '@goobits/ui/services';Usage:
// Check rate limit
const result = await rateLimitFormSubmission({
ipAddress: request.headers.get('x-forwarded-for'),
email: 'user@example.com',
maxRequests: 3,
windowMs: 60000
});
if (!result.allowed) {
return { error: 'Rate limit exceeded', retryAfter: result.retryAfter };
}reCAPTCHA verification and provider creation.
Import:
import { createRecaptchaProvider } from '@goobits/ui/services';Usage:
// Create provider
const recaptcha = createRecaptchaProvider({
provider: 'google-v3',
siteKey: process.env.RECAPTCHA_SITE_KEY,
secretKey: process.env.RECAPTCHA_SECRET_KEY
});
// Execute challenge (client-side)
const token = await recaptcha.execute('contact_form');
// Verify token (server-side)
const result = await recaptcha.verify(token);
if (result.score < 0.5) {
return { error: 'Failed security check' };
}All components and utilities include TypeScript type definitions. Import types:
import type {
ContactConfig,
CategoryConfig,
RecaptchaConfig,
FileSettings
} from '@goobits/ui/config';
import type {
MenuItem,
MenuConfig,
TooltipOptions
} from '@goobits/ui/ui';See TypeScript Guide for type documentation.
See also:
- TypeScript Guide - Type-safe development
- Configuration Guide - Configuration options
- Troubleshooting - Common issues