A modern, standardized Svelte 5 component library designed for simplicity, consistency, and AI-friendly usage patterns.
- Standardized API with consistent prop naming and patterns across components
- Enum-based properties for predictable component customization
- Strong TypeScript support with comprehensive type definitions
- Utility-first approach built with TailwindCSS
- Accessible components adhering to modern web standards
- Simplified component consumption ideal for both human and AI developers
Install the project
npm i @makolabs/ripple
Import ripple UI components
<script lang="ts">
import { Button, Card, Modal } from '@makolabs/ripple';
</script>
<div class="px-12 pt-12">
<Card title="Hello World" color="warning">
<p>This is a card component</p>
</Card>
</div>
Paste the following CSS import code in app.css
@source "../node_modules/@makolabs/ripple";
@theme {
/* Default (default) */
--color-default-50: oklch(0.984 0.003 247.858);
--color-default-100: oklch(0.96 0.006 247.858);
--color-default-200: oklch(0.91 0.008 247.858);
--color-default-300: oklch(0.85 0.01 247.858);
--color-default-400: oklch(0.76 0.012 247.858);
--color-default-500: oklch(0.65 0.015 247.858);
--color-default-600: oklch(0.54 0.018 247.858);
--color-default-700: oklch(0.45 0.015 247.858);
--color-default-800: oklch(0.35 0.012 247.858);
--color-default-900: oklch(0.25 0.01 247.858);
--color-default-950: oklch(0.15 0.008 247.858);
/* Primary (Blue) */
--color-primary-50: oklch(0.97 0.025 250);
--color-primary-100: oklch(0.94 0.035 250);
--color-primary-200: oklch(0.89 0.055 250);
--color-primary-300: oklch(0.82 0.075 250);
--color-primary-400: oklch(0.74 0.095 250);
--color-primary-500: oklch(0.65 0.115 250);
--color-primary-600: oklch(0.55 0.125 250);
--color-primary-700: oklch(0.45 0.115 250);
--color-primary-800: oklch(0.35 0.095 250);
--color-primary-900: oklch(0.25 0.075 250);
--color-primary-950: oklch(0.15 0.055 250);
/* Secondary (Slate) */
--color-secondary-50: oklch(0.97 0.02 255);
--color-secondary-100: oklch(0.94 0.03 255);
--color-secondary-200: oklch(0.89 0.04 255);
--color-secondary-300: oklch(0.82 0.05 255);
--color-secondary-400: oklch(0.74 0.06 255);
--color-secondary-500: oklch(0.65 0.07 255);
--color-secondary-600: oklch(0.55 0.065 255);
--color-secondary-700: oklch(0.45 0.055 255);
--color-secondary-800: oklch(0.35 0.045 255);
--color-secondary-900: oklch(0.25 0.035 255);
--color-secondary-950: oklch(0.15 0.025 255);
/* Info (Sky) */
--color-info-50: oklch(0.97 0.025 220);
--color-info-100: oklch(0.94 0.04 220);
--color-info-200: oklch(0.89 0.06 220);
--color-info-300: oklch(0.82 0.085 220);
--color-info-400: oklch(0.74 0.105 220);
--color-info-500: oklch(0.65 0.125 220);
--color-info-600: oklch(0.55 0.115 220);
--color-info-700: oklch(0.45 0.105 220);
--color-info-800: oklch(0.35 0.085 220);
--color-info-900: oklch(0.25 0.065 220);
--color-info-950: oklch(0.15 0.045 220);
/* Success (Green) */
--color-success-50: oklch(0.97 0.025 145);
--color-success-100: oklch(0.94 0.04 145);
--color-success-200: oklch(0.89 0.06 145);
--color-success-300: oklch(0.82 0.08 145);
--color-success-400: oklch(0.74 0.1 145);
--color-success-500: oklch(0.65 0.12 145);
--color-success-600: oklch(0.55 0.11 145);
--color-success-700: oklch(0.45 0.1 145);
--color-success-800: oklch(0.35 0.08 145);
--color-success-900: oklch(0.25 0.06 145);
--color-success-950: oklch(0.15 0.04 145);
/* Warning (Yellow) */
--color-warning-50: oklch(0.97 0.025 90);
--color-warning-100: oklch(0.94 0.045 90);
--color-warning-200: oklch(0.89 0.065 90);
--color-warning-300: oklch(0.82 0.085 90);
--color-warning-400: oklch(0.74 0.105 90);
--color-warning-500: oklch(0.65 0.125 90);
--color-warning-600: oklch(0.55 0.115 90);
--color-warning-700: oklch(0.45 0.105 90);
--color-warning-800: oklch(0.35 0.085 90);
--color-warning-900: oklch(0.25 0.065 90);
--color-warning-950: oklch(0.15 0.045 90);
/* Danger (Red) */
--color-danger-50: oklch(0.97 0.025 25);
--color-danger-100: oklch(0.94 0.045 25);
--color-danger-200: oklch(0.89 0.065 25);
--color-danger-300: oklch(0.82 0.085 25);
--color-danger-400: oklch(0.74 0.105 25);
--color-danger-500: oklch(0.65 0.125 25);
--color-danger-600: oklch(0.55 0.115 25);
--color-danger-700: oklch(0.45 0.105 25);
--color-danger-800: oklch(0.35 0.085 25);
--color-danger-900: oklch(0.25 0.065 25);
--color-danger-950: oklch(0.15 0.045 25);
}
Ripple UI was built with a focus on consistency and standardization. Every component follows the same patterns for customization:
Components use standardized enums for colors, sizes, and variants:
// Colors available for most components
Color.DEFAULT // 'default'
Color.PRIMARY // 'primary'
Color.SECONDARY // 'secondary'
Color.INFO // 'info'
Color.SUCCESS // 'success'
Color.WARNING // 'warning'
Color.DANGER // 'danger'
// Sizes available for most components
Size.XS // 'xs'
Size.SM // 'sm'
Size.BASE // 'base'
Size.LG // 'lg'
Size.XL // 'xl'
Size.XXL // '2xl'
All components follow a consistent props pattern with predictable naming:
-
color
: Component color theme (using the Color enum) -
size
: Component size (using the Size enum) -
class
: Custom CSS classes for the component - Event handlers with
on
prefix (e.g.,onclick
,onchange
) - Element-specific class props named with component + 'class' (e.g.,
titleclass
,bodyclass
)
Most components in Ripple UI support variants to customize their appearance. Here are some examples:
Buttons come with different variants, colors, sizes, and shapes:
<script lang="ts">
import { Button, Color, Size } from '@makolabs/ripple';
</script>
<!-- Different button variants -->
<Button variant="solid" color={Color.PRIMARY}>Solid Button</Button>
<Button variant="outline" color={Color.SECONDARY}>Outline Button</Button>
<Button variant="ghost" color={Color.DANGER}>Ghost Button</Button>
<Button variant="link" color={Color.INFO}>Link Button</Button>
<!-- Button with onclick handler -->
<Button
color={Color.SUCCESS}
onclick={() => console.log('Button clicked')}
>
Click Me
</Button>
<!-- Button as link -->
<Button
href="https://example.com"
target="_blank"
color={Color.PRIMARY}
>
Visit Website
</Button>
<!-- Button sizes -->
<Button size={Size.XS}>Extra Small</Button>
<Button size={Size.SM}>Small</Button>
<Button size={Size.BASE}>Base</Button>
<Button size={Size.LG}>Large</Button>
<Button size={Size.XL}>Extra Large</Button>
<Button size={Size.XXL}>2X Large</Button>
<!-- Button variants with different colors -->
<Button variant="solid" color={Color.PRIMARY}>Primary Solid</Button>
<Button variant="solid" color={Color.DANGER}>Danger Solid</Button>
<Button variant="outline" color={Color.SUCCESS}>Success Outline</Button>
<Button variant="ghost" color={Color.WARNING}>Warning Ghost</Button>
<Button variant="link" color={Color.INFO}>Info Link</Button>
Modals with different sizes and custom content:
<script lang="ts">
import { Modal, Button, Size } from '@makolabs/ripple';
let isOpen = false;
</script>
<Button onclick={() => isOpen = true}>Open Modal</Button>
<!-- Basic modal -->
<Modal
open={isOpen}
title="Basic Modal"
size={Size.BASE}
onClose={() => isOpen = false}
>
<p>Modal content goes here</p>
</Modal>
<!-- Modal with different size -->
<Modal
open={isOpen}
title="Large Modal"
size={Size.XL}
onClose={() => isOpen = false}
>
<p>This modal is larger and provides more content space</p>
</Modal>
<!-- Modal with custom header and footer -->
<Modal
open={isOpen}
onClose={() => isOpen = false}
size={Size.BASE}
>
<svelte:fragment slot="header">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 16 16">
<path d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<h3 class="text-lg font-medium">Custom Header</h3>
</div>
</svelte:fragment>
<p>Modal with custom header and footer</p>
<svelte:fragment slot="footer">
<div class="flex justify-end space-x-2">
<Button variant="outline" onclick={() => isOpen = false}>Cancel</Button>
<Button color={Color.PRIMARY}>Save Changes</Button>
</div>
</svelte:fragment>
</Modal>
<!-- TODO: Remove position prop from Modal component in future versions -->
Drawers can slide in from different edges of the screen:
<script lang="ts">
import { Drawer, Button } from '@makolabs/ripple';
let isDrawerOpen = false;
</script>
<Button onclick={() => isDrawerOpen = true}>Open Drawer</Button>
<Drawer
open={isDrawerOpen}
position="right"
onClose={() => isDrawerOpen = false}
>
<div class="p-4">
<h3 class="text-lg font-medium mb-4">Drawer Title</h3>
<p class="mb-4">This is a drawer that slides in from the side of the screen.</p>
<Button onclick={() => isDrawerOpen = false}>Close Drawer</Button>
</div>
</Drawer>
A component for consistent page headers:
<script lang="ts">
import { PageHeader, Button, Color } from '@makolabs/ripple';
const breadcrumbs = [
{ label: 'Dashboard', href: '#' },
{ label: 'Projects', href: '#' },
{ label: 'Current Project' }
];
</script>
<PageHeader
title="Project Dashboard"
description="View and manage your project details"
breadcrumbs={breadcrumbs}
>
<svelte:fragment slot="actions">
<Button color={Color.PRIMARY}>New Project</Button>
</svelte:fragment>
</PageHeader>
Cards can be customized with different styles:
<script lang="ts">
import { Card, StatsCard, Color } from '@makolabs/ripple';
</script>
<Card title="Basic Card" color={Color.PRIMARY}>
<p>Card content goes here</p>
</Card>
<StatsCard
label="Monthly Sales"
value="$865,000"
previousValue="$750,000"
previousValuePrefix="vs"
trend={15.3}
color={Color.SUCCESS}
chartData={[20, 25, 30, 22, 35, 40, 38, 45, 50]}
icon={
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 16 16">
<path d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
}
/>
Tables for displaying structured data with pagination and sorting:
<script lang="ts">
import { Table, Color, Size } from '@makolabs/ripple';
let data = [
{ id: 1, name: 'John Doe', email: 'john@example.com', status: 'Active' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', status: 'Inactive' },
{ id: 3, name: 'Robert Johnson', email: 'robert@example.com', status: 'Active' }
];
const columns = [
{ key: 'name', label: 'Name', sortable: true },
{ key: 'email', label: 'Email', sortable: true },
{ key: 'status', label: 'Status' }
];
let selected = [];
let sort = { column: 'name', direction: 'asc' };
</script>
<Table
data={data}
{columns}
color={Color.PRIMARY}
size={Size.BASE}
pageSize={10}
selectable={true}
bind:selected={selected}
bind:sort={sort}
striped={true}
/>
Tabs for organizing content into different views:
<script lang="ts">
import { TabGroup, TabContent, Color, Size } from '@makolabs/ripple';
const tabs = [
{ value: 'overview', label: 'Overview' },
{ value: 'details', label: 'Details' },
{ value: 'settings', label: 'Settings' }
];
let activeTab = 'overview';
function handleTabChange(value) {
console.log(`Tab changed to ${value}`);
}
</script>
<TabGroup
tabs={tabs}
bind:selected={activeTab}
onchange={handleTabChange}
color={Color.PRIMARY}
size={Size.BASE}
>
<TabContent value="overview" persisted>
<p>Overview content here</p>
</TabContent>
<TabContent value="details" persisted>
<p>Details content here</p>
</TabContent>
<TabContent value="settings" persisted>
<p>Settings content here</p>
</TabContent>
</TabGroup>
Badges for displaying statuses and counts:
<script lang="ts">
import { Badge, Color, Size } from '@makolabs/ripple';
</script>
<Badge color={Color.PRIMARY} size={Size.BASE}>New</Badge>
<Badge color={Color.SUCCESS}>Success</Badge>
<Badge color={Color.WARNING}>Warning</Badge>
<Badge color={Color.DANGER}>43</Badge>
Dropdown selector for choosing from a list of options:
<script lang="ts">
import { Select, Size } from '@makolabs/ripple';
const items = [
{ label: 'Option 1', value: 'option1' },
{ label: 'Option 2', value: 'option2' },
{ label: 'Option 3', value: 'option3', disabled: true },
{ label: 'Option 4', value: 'option4' }
];
let selected = 'option1';
function handleSelect(event) {
console.log('Selected:', event.value);
}
</script>
<Select
{items}
bind:value={selected}
class="w-64"
size={Size.BASE}
onselect={handleSelect}
/>
Menu dropdown for actions and navigation:
<script lang="ts">
import { Dropdown, Size } from '@makolabs/ripple';
import FluentChevronDown16Filled from '$icons/FluentChevronDown16Filled.svelte';
const sections = [
{
items: [
{
label: 'Edit',
icon: FluentPen16Filled,
onclick: () => console.log('Edit clicked')
},
{
label: 'Duplicate',
icon: FluentPenSparkle24Filled,
onclick: () => console.log('Duplicate clicked')
}
]
},
{
items: [
{
label: 'Delete',
icon: FluentDelete24Filled,
onclick: () => console.log('Delete clicked')
}
]
}
];
</script>
<Dropdown
sections={sections}
label="Actions"
size={Size.BASE}
icon={FluentChevronDown16Filled}
/>
Ripple UI components are designed to work together seamlessly:
<script lang="ts">
import { Card, TabGroup, TabContent, Button, Color, Size } from '@makolabs/ripple';
const tabs = [
{ value: 'overview', label: 'Overview' },
{ value: 'details', label: 'Details' },
{ value: 'settings', label: 'Settings' }
];
let activeTab = 'overview';
</script>
<Card title="Project Information" color={Color.PRIMARY}>
<TabGroup
tabs={tabs}
bind:selected={activeTab}
color={Color.INFO}
size={Size.BASE}
>
<TabContent value="overview">
<p>Project overview content here...</p>
<Button variant="solid" color={Color.SUCCESS} size={Size.SM}>
Take Action
</Button>
</TabContent>
<TabContent value="details">
<p>Project details content here...</p>
</TabContent>
<TabContent value="settings">
<p>Project settings content here...</p>
</TabContent>
</TabGroup>
</Card>
Ripple UI now exports all components from a central entry point, making it easier to import components:
<script lang="ts">
import { Button, Modal, Card, Table, Select, Dropdown } from '@makolabs/ripple';
</script>
You can still import specific component types when needed:
<script lang="ts">
import { Button, type ButtonProps } from '@makolabs/ripple';
// Create a custom button with specific props
const myButton: ButtonProps = {
variant: 'outline',
color: 'primary',
size: 'lg',
rounded: 'xl'
};
</script>
<Button {...myButton}>Custom Button</Button>