Button
A versatile button component with multiple variants, sizes, loading states, and icons. Built for Pulse Framework with full reactivity support.
Live Code Editor
Modifiez le code ci-dessous et voyez le résultat en temps réel :
Import
import { Button, Pulse } from '@odyssee/components';Basic Usage
const button = Button({
variant: 'solid',
color: 'primary',
children: 'Click me'
});2
3
4
5
Variants
The Button component supports five variants: solid, outline, ghost, soft, and link.
Button({ variant: 'solid', children: 'Solid Button' })
Button({ variant: 'outline', children: 'Outline Button' })
Button({ variant: 'ghost', children: 'Ghost Button' })
Button({ variant: 'soft', children: 'Soft Button' })
Button({ variant: 'link', children: 'Link Button' })2
3
4
5
Colors
Buttons support multiple color schemes: primary, secondary, success, danger, warning, info, light, and dark.
Button({ color: 'primary', children: 'Primary' })
Button({ color: 'success', children: 'Success' })
Button({ color: 'danger', children: 'Danger' })2
3
Sizes
Five size options are available: xs, sm, md, lg, and xl.
Button({ size: 'xs', children: 'Extra Small' })
Button({ size: 'md', children: 'Medium' })
Button({ size: 'xl', children: 'Extra Large' })2
3
Loading State
Show a loading spinner and disable the button during async operations.
const isLoading = Pulse.signal(false);
const handleSubmit = async () => {
isLoading(true);
try {
await someAsyncOperation();
} finally {
isLoading(false);
}
};
const loadingBtn = (
<Button
loading={isLoading}
onClick={handleSubmit}
>
Submit
</Button>
);2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Static Loading State
const staticLoadingBtn = (
<Button loading={true}>
Loading...
</Button>
);2
3
4
5
With Icons
Add icons to buttons with customizable positioning.
// Icon on the left (default)
const iconLeftBtn = (
<Button icon="🚀" iconPosition="left">
Launch
</Button>
);
// Icon on the right
const iconRightBtn = (
<Button icon="→" iconPosition="right">
Next
</Button>
);
// Icon only
const iconOnlyBtn = (
<Button icon="❤️" />
);2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Disabled State
const disabledBtn = (
<Button disabled={true}>
Disabled Button
</Button>
);2
3
4
5
Reactive Disabled State
const email = Pulse.signal('');
const password = Pulse.signal('');
const isFormValid = Pulse.computed(() =>
email().includes('@') && password().length >= 8
);
const submitBtn = (
<Button
disabled={Pulse.computed(() => !isFormValid())}
type="submit"
>
Sign In
</Button>
);2
3
4
5
6
7
8
9
10
11
12
13
14
15
Full Width
const fullWidthBtn = (
<Button fullWidth={true}>
Full Width Button
</Button>
);2
3
4
5
Button Types
Specify the HTML button type attribute.
// Submit button (for forms)
const submitBtn = (
<Button type="submit">Submit</Button>
);
// Reset button
const resetBtn = (
<Button type="reset">Reset</Button>
);
// Button (default)
const regularBtn = (
<Button type="button">Click</Button>
);2
3
4
5
6
7
8
9
10
11
12
13
14
Click Handlers
const count = Pulse.signal(0);
const counterBtn = (
<Button onClick={() => count(count() + 1)}>
Clicked {count()} times
</Button>
);2
3
4
5
6
7
Custom Styling
Add custom classes with Tailwind CSS.
const customBtn = (
<Button className="shadow-lg hover:shadow-xl">
Custom Styled
</Button>
);2
3
4
5
Complete Example
Here's a comprehensive example combining multiple features:
import { Button, Input, Pulse } from '@odyssee/components';
const LoginForm = () => {
const email = Pulse.signal('');
const password = Pulse.signal('');
const isLoading = Pulse.signal(false);
const isValid = Pulse.computed(() =>
email().includes('@') && password().length >= 8
);
const handleSubmit = async (e: Event) => {
e.preventDefault();
isLoading(true);
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('Login successful!');
} catch (error) {
console.error('Login failed:', error);
} finally {
isLoading(false);
}
};
return (
<form onsubmit={handleSubmit} class="space-y-4 max-w-md">
<Input
type="email"
label="Email"
value={email}
onChange={(val) => email(val)}
placeholder="you@example.com"
/>
<Input
type="password"
label="Password"
value={password}
onChange={(val) => password(val)}
placeholder="••••••••"
/>
<div class="flex gap-2">
<Button
type="submit"
color="primary"
loading={isLoading}
disabled={Pulse.computed(() => !isValid())}
fullWidth={true}
>
Sign In
</Button>
<Button
type="button"
variant="ghost"
onClick={() => {
email('');
password('');
}}
>
Clear
</Button>
</div>
</form>
);
};2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
Props
| Prop | Type | Default | Description |
|---|---|---|---|
type | "button" | "submit" | "reset" | "button" | HTML button type |
variant | "solid" | "outline" | "ghost" | "soft" | "link" | "solid" | Button variant style |
color | "primary" | "secondary" | "success" | "danger" | "warning" | "info" | "light" | "dark" | "primary" | Button color scheme |
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | Button size |
disabled | boolean | Signal<boolean> | false | Disable button |
loading | boolean | Signal<boolean> | false | Show loading state |
icon | string | - | Icon to display (emoji or text) |
iconPosition | "left" | "right" | "left" | Icon position relative to text |
fullWidth | boolean | false | Make button full width |
onClick | (event: Event) => void | - | Click event handler |
children | string | HTMLElement | Array<string | HTMLElement> | - | Button content |
className | string | - | Additional CSS classes |
id | string | - | HTML id attribute |
style | string | CSSStyleDeclaration | - | Inline styles |
Accessibility
The Button component follows accessibility best practices:
- ✅ Proper
typeattribute for form contexts - ✅
disabledattribute when not interactive - ✅ Loading state includes
aria-label="Loading" - ✅ Keyboard navigation support (Enter/Space)
- ✅ Focus ring for keyboard users
- ✅ Sufficient color contrast ratios
ARIA Attributes
You can add custom ARIA attributes:
const ariaBtn = (
<Button
aria-label="Close dialog"
aria-expanded="false"
>
✕
</Button>
);2
3
4
5
6
7
8
Best Practices
✅ Do
- Use appropriate button types (
submitfor forms) - Provide loading states for async actions
- Use meaningful button text or aria-labels
- Disable buttons when actions are not available
- Use appropriate colors (danger for destructive actions)
// Good: Clear intent, proper type, loading state
const deleteBtn = (
<Button
type="button"
color="danger"
loading={isDeleting}
onClick={handleDelete}
>
Delete Account
</Button>
);2
3
4
5
6
7
8
9
10
11
❌ Don't
- Don't use buttons for navigation (use links instead)
- Don't omit loading states for async operations
- Don't use vague labels like "Click here"
- Don't disable without explanation
// Bad: Navigation should use <a> tag
const navBtn = (
<Button onClick={() => navigate('/home')}>
Go Home
</Button>
);
// Better: Use anchor tag for navigation
const navLink = (
<a href="/home" class="btn btn-primary">
Go Home
</a>
);2
3
4
5
6
7
8
9
10
11
12
13
Styling & Theming
All button styles use Tailwind CSS classes and support dark mode automatically:
// Automatically adapts to dark mode
const adaptiveBtn = (
<Button variant="solid" color="primary">
I look great in dark mode too!
</Button>
);2
3
4
5
6
Custom Theme Colors
You can extend the color palette by adding custom classes:
const customColorBtn = (
<Button className="bg-purple-600 hover:bg-purple-700 text-white">
Custom Purple
</Button>
);2
3
4
5
TypeScript
Full TypeScript support with complete type definitions:
import type { ButtonProps } from '@odyssee/components';
const props: ButtonProps = {
variant: 'solid',
color: 'primary',
size: 'md',
onClick: (e: Event) => {
console.log('Clicked!');
}
};
const typedBtn = <Button {...props}>Typed Button</Button>;2
3
4
5
6
7
8
9
10
11
12
Related Components
Version: 1.0.0
Last Updated: January 2025