Skip to main content
This guide will help you build your first UI with Kivora React components. We’ll create a simple contact form using Button, Input, and Badge components.

Prerequisites

Before you begin, make sure you’ve:
  1. Installed Kivora React
  2. Imported the global stylesheet
  3. Set up your React project (Next.js, Vite, CRA, etc.)

Your First Component

Let’s start with a simple button:
import { Button } from '@kivora/react';

export default function App() {
  return (
    <Button 
      label="Save changes" 
      variant="primary" 
      size="md"
      onClick={() => alert('Saved!')}
    />
  );
}
This renders a primary button with medium size. The onClick handler is called when the button is clicked.
Hover over the Button component in your editor to see all available props and their documentation.

Button Variants

Kivora provides several semantic variants:
import { Button } from '@kivora/react';

export default function ButtonVariants() {
  return (
    <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
      <Button label="Primary" variant="primary" />
      <Button label="Secondary" variant="secondary" />
      <Button label="Ghost" variant="ghost" />
      <Button label="Destructive" variant="destructive" />
      <Button label="Outline" variant="outline" />
    </div>
  );
}

Working with Input

The Input component supports labels, error states, and different sizes:
import { Input } from '@kivora/react';
import { useState } from 'react';

export default function InputExample() {
  const [email, setEmail] = useState('');

  return (
    <Input
      label="Email"
      type="email"
      placeholder="[email protected]"
      value={email}
      onChange={setEmail}
      size="md"
    />
  );
}
The onChange prop receives the string value directly, not the DOM event. This makes it easier to work with controlled inputs.

Adding Validation

Show error states with the error prop:
import { Input } from '@kivora/react';
import { useState } from 'react';

export default function ValidatedInput() {
  const [email, setEmail] = useState('');
  const [error, setError] = useState<string>();

  const validateEmail = (value: string) => {
    if (!value.includes('@')) {
      setError('Please enter a valid email');
    } else {
      setError(undefined);
    }
  };

  return (
    <Input
      label="Email"
      type="email"
      placeholder="[email protected]"
      value={email}
      onChange={(value) => {
        setEmail(value);
        validateEmail(value);
      }}
      error={error}
    />
  );
}

Using Badges

Badges are perfect for status indicators:
import { Badge } from '@kivora/react';

export default function StatusBadges() {
  return (
    <div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
      <Badge variant="success">Active</Badge>
      <Badge variant="warning">Pending</Badge>
      <Badge variant="error">Failed</Badge>
      <Badge variant="info">Info</Badge>
      <Badge variant="default">Default</Badge>
    </div>
  );
}

Building a Contact Form

Now let’s combine everything into a complete contact form:
import { Button, Input, Badge } from '@kivora/react';
import { useState } from 'react';

export default function ContactForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [loading, setLoading] = useState(false);
  const [status, setStatus] = useState<'idle' | 'success' | 'error'>('idle');

  const handleSubmit = async () => {
    setLoading(true);
    setStatus('idle');

    try {
      // Simulate API call
      await new Promise((resolve) => setTimeout(resolve, 1000));
      setStatus('success');
      setName('');
      setEmail('');
    } catch (error) {
      setStatus('error');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div style={{ maxWidth: '400px', padding: '2rem' }}>
      <h2>Contact Us</h2>
      
      <div style={{ marginBottom: '1rem' }}>
        <Input
          label="Name"
          placeholder="Your name"
          value={name}
          onChange={setName}
        />
      </div>

      <div style={{ marginBottom: '1rem' }}>
        <Input
          label="Email"
          type="email"
          placeholder="[email protected]"
          value={email}
          onChange={setEmail}
        />
      </div>

      <Button
        label="Submit"
        variant="primary"
        loading={loading}
        onClick={handleSubmit}
      />

      {status === 'success' && (
        <div style={{ marginTop: '1rem' }}>
          <Badge variant="success">Message sent successfully!</Badge>
        </div>
      )}

      {status === 'error' && (
        <div style={{ marginTop: '1rem' }}>
          <Badge variant="error">Failed to send message</Badge>
        </div>
      )}
    </div>
  );
}

Using Hooks

Kivora React includes powerful hooks. Let’s enhance our form with useDisclosure to show a success modal:
import { Button, Input, Modal, useDisclosure } from '@kivora/react';
import { useState } from 'react';

export default function FormWithModal() {
  const [email, setEmail] = useState('');
  const { isOpen, open, close } = useDisclosure();

  const handleSubmit = async () => {
    // Submit form...
    open(); // Show success modal
  };

  return (
    <>
      <div style={{ padding: '2rem' }}>
        <Input
          label="Email"
          type="email"
          value={email}
          onChange={setEmail}
        />
        <Button label="Subscribe" onClick={handleSubmit} />
      </div>

      <Modal open={isOpen} onClose={close} title="Success!">
        <p>Thank you for subscribing!</p>
      </Modal>
    </>
  );
}

Debouncing User Input

Use useDebounce to delay expensive operations like API calls:
import { Input, useDebounce } from '@kivora/react';
import { useState, useEffect } from 'react';

export default function SearchInput() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 300);

  useEffect(() => {
    if (debouncedQuery) {
      // Only runs 300ms after user stops typing
      console.log('Searching for:', debouncedQuery);
      // fetchResults(debouncedQuery);
    }
  }, [debouncedQuery]);

  return (
    <Input
      label="Search"
      placeholder="Type to search..."
      value={query}
      onChange={setQuery}
    />
  );
}

Copying to Clipboard

The useClipboard hook makes it easy to add copy functionality:
import { Button, useClipboard } from '@kivora/react';

export default function CopyButton() {
  const { copy, copied } = useClipboard();

  return (
    <Button
      label={copied ? 'Copied!' : 'Copy to clipboard'}
      variant={copied ? 'success' : 'secondary'}
      onClick={() => copy('Hello, world!')}
    />
  );
}

Next Steps

You now know the basics of Kivora React! Here’s what to explore next:

Theming

Customize colors and design tokens

Dark Mode

Implement theme switching

TypeScript

Learn about TypeScript support

All Components

Explore the full component library

Build docs developers (and LLMs) love