SolidJS Installation
Prerequisites
- Node.js (v18 or higher)
- pnpm package manager (or npm/yarn)
- Basic knowledge of SolidJS, Vite, and TypeScript
- A text editor or IDE
This guide uses Vite with SolidJS. The steps may vary slightly for other build tools or SolidJS meta-frameworks like SolidStart.
Step 1: Create a SolidJS Project
Start by creating a new SolidJS project using the official SolidJS template:
pnpm create solid
cd solid-project
pnpm devThis creates a new SolidJS project with TypeScript and Vite enabled. Once the project is created, navigate to the project directory and start the development server to verify everything is working.
Step 2: Install Utility Dependencies
Ocean UI components use utility functions for merging Tailwind CSS classes. Install the required dependencies:
pnpm i tailwind-merge clsxThese packages help merge and resolve Tailwind CSS class names, preventing conflicts and ensuring proper styling.
Step 3: Create the CN Utility Function
Create a utility function that combines clsx and tailwind-merge for
optimal class name handling. This function is used throughout Ocean UI
components.
Create a new file src/lib/utils.ts:
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}The cn function intelligently merges Tailwind classes, ensuring that
conflicting classes (like p-4 and p-2) resolve correctly, with the last
one taking precedence.
Step 4: Install Core Dependencies
Ocean UI components are built on top of Ark UI primitives and use Lucide Solid for icons. Install these core dependencies:
pnpm install @ark-ui/solid lucide-solid- @ark-ui/solid: Provides accessible, headless UI primitives for SolidJS - lucide-solid: Icon library used throughout Ocean UI components
Step 5: Copy Base Components
Base components are the core, reusable components that wrap Ark UI primitives. These are the building blocks for your UI.
Copy the base component you want to use from Ocean UI's component library. For example, to use the Accordion component:
Create src/components/ui/accordion.tsx:
import {
AccordionRoot,
AccordionItem as AccordionItemPrimitive,
AccordionItemTrigger as AccordionItemTriggerPrimitive,
AccordionItemContent as AccordionItemContentPrimitive,
type AccordionRootProps,
type AccordionItemProps,
type AccordionItemTriggerProps,
type AccordionItemContentProps,
} from "@ark-ui/solid/accordion";
import { ChevronDownIcon } from "lucide-solid";
import type { JSX, ParentComponent } from "solid-js";
import { splitProps } from "solid-js";
import { cn } from "@/lib/utils";
export interface AccordionProps extends AccordionRootProps {}
export const Accordion: ParentComponent<AccordionProps> = (props) => {
const [local, rest] = splitProps(props, ["class", "children"]);
return (
<AccordionRoot class={cn("w-full", local.class)} {...rest}>
{local.children}
</AccordionRoot>
);
};
export interface AccordionItemComponentProps extends AccordionItemProps {}
export const AccordionItemComponent: ParentComponent<
AccordionItemComponentProps
> = (props) => {
const [local, rest] = splitProps(props, ["class", "children"]);
return (
<AccordionItemPrimitive
class={cn("border-b border-border last:border-b-0", local.class)}
{...rest}
>
{local.children}
</AccordionItemPrimitive>
);
};
export interface AccordionTriggerComponentProps
extends AccordionItemTriggerProps {
/**
* Optional icon or content to display on the left side
*/
leftIcon?: JSX.Element;
/**
* Optional icon or content to display on the right side
* If not provided, defaults to ChevronDownIcon
*/
rightIcon?: JSX.Element;
}
export const AccordionTriggerComponent: ParentComponent<
AccordionTriggerComponentProps
> = (props) => {
const [local, rest] = splitProps(props, [
"class",
"children",
"leftIcon",
"rightIcon",
]);
return (
<AccordionItemTriggerPrimitive
class={cn(
"group flex w-full items-center gap-4 py-4 text-left cursor-pointer text-sm font-medium transition-all outline-none",
"hover:underline",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
"disabled:pointer-events-none disabled:opacity-50",
"[&[data-state=open]>.accordion-chevron]:rotate-180",
"data-[state=closed]:text-primary/60 data-[state=closed]:hover:text-primary data-[state=open]:text-primary",
local.class
)}
{...rest}
>
{/* Left Icon Slot */}
{local.leftIcon}
{/* Title/Content */}
<span class="flex-1">{local.children}</span>
{/* Right Icon Slot */}
{local.rightIcon ? (
<span class="ml-auto">{local.rightIcon}</span>
) : (
<ChevronDownIcon class="accordion-chevron text-muted-foreground pointer-events-none size-4 shrink-0 transition-transform duration-200 ml-auto" />
)}
</AccordionItemTriggerPrimitive>
);
};
export interface AccordionContentComponentProps
extends AccordionItemContentProps {}
export const AccordionContentComponent: ParentComponent<
AccordionContentComponentProps
> = (props) => {
const [local, rest] = splitProps(props, ["class", "children"]);
return (
<AccordionItemContentPrimitive
class={cn(
"overflow-hidden text-sm",
// Ark UI recommended animations
"data-[state=closed]:opacity-0 data-[state=open]:opacity-100",
"data-[state=closed]:animate-accordion-collapse data-[state=open]:animate-accordion-expand",
// Ark UI recommended data selectors for custom animations:
"[data-scope=accordion][data-part=item-content][data-state=open]:animate-[slideDown_250ms_ease-in-out]",
"[data-scope=accordion][data-part=item-content][data-state=closed]:animate-[slideUp_200ms_ease-in-out]",
local.class
)}
{...rest}
>
<div class="pb-4">{local.children}</div>
</AccordionItemContentPrimitive>
);
};
// Export with cleaner names
export {
AccordionItemComponent as AccordionItem,
AccordionTriggerComponent as AccordionTrigger,
AccordionContentComponent as AccordionContent,
};Base components are reusable and can be customized. They handle the core
functionality and styling, but you can extend them with additional props or
styling as needed. Note that SolidJS uses class instead of className, and
components use ParentComponent with splitProps for prop handling.
Step 6: Set Up Token CSS
Ocean UI uses a comprehensive design token system for consistent styling. The
token.css file contains all color variables, spacing, typography, shadows,
and animations used throughout the components.
Copy the token.css file from Ocean UI and add it to your project. Create
src/token.css with the following content:
Create src/token.css:
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
/* Transparent color */
--color-transparent: oklch(0 0 0 / 0);
/* Animations */
--animate-flip: flip 6s infinite steps(2, end);
--animate-rotate: rotate 3s linear infinite both;
@keyframes flip {
to {
transform: rotate(360deg);
}
}
@keyframes rotate {
to {
transform: rotate(90deg);
}
}
/* Accordion */
--animate-accordion-collapse: accordion-collapse var(--tw-duration, 200ms)
ease-out;
--animate-accordion-expand: accordion-expand var(--tw-duration, 200ms)
ease-out;
@keyframes accordion-collapse {
from {
height: var(--height);
}
to {
height: 0;
}
}
@keyframes accordion-expand {
from {
height: 0;
}
to {
height: var(--height);
}
}
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
/* Fumadocs */
--color-fd-diff-remove: oklch(0.88 0.06 18 / 0.7);
--color-fd-diff-add: oklch(0.9 0.09 164 / 0.5);
--color-fd-diff-add-symbol: oklch(0.45 0.11 151);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
/* Fumadocs */
--color-fd-diff-remove: oklch(0.64 0.21 25 / 0.4);
--color-fd-diff-remove-symbol: oklch(0.81 0.1 20);
--color-fd-diff-add: oklch(0.87 0.14 154 / 0.25);
--color-fd-diff-add-symbol: oklch(0.96 0.04 157);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
h1,
h2,
h3,
h4,
h5,
h6 {
@apply tracking-tight;
}
}
Then import it in your main entry file (typically src/index.css or
src/app.css):
@import "tailwindcss";
@import "./token.css";The token.css file is essential for Ocean UI components to look and function correctly. It includes: - Color system (light and dark mode variables) - Typography scales - Spacing system - Border radius values - Shadow definitions
- Animation keyframes - Component-specific tokens
You can modify token.css to customize the design system to match your brand,
but ensure you maintain the required CSS variable names that components depend
on.
Step 7: Create Example Components
Example components (also called "shared" or "demo" components) are complete, ready-to-use implementations that demonstrate how to use base components in real scenarios.
Create an example component that uses your base component. For instance,
create src/components/shared/faq-accordion.tsx:
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { For } from "solid-js";
const accordionItems = [
{
value: "item-1",
title: "How do I update my account information?",
content:
'You can update your account information by navigating to your profile settings. Click on your profile icon in the top right corner, then select "Account Settings" from the dropdown menu. From there, you can edit your name, email, password, and other personal details.',
},
{
value: "item-2",
title: "What payment methods are accepted?",
content:
"We accept all major credit cards (Visa, Mastercard, American Express), debit cards, PayPal, Apple Pay, and Google Pay. All transactions are processed securely through our encrypted payment gateway to ensure your financial information is protected.",
},
{
value: "item-3",
title: "How can I track my order?",
content:
"Once your order has been shipped, you'll receive a tracking number via email. You can use this tracking number on our website's tracking page or on the carrier's website to monitor your package's journey in real-time. You'll receive updates at each stage of the delivery process.",
},
];
export default function AccordionDemo() {
return (
<Accordion class="w-full max-w-lg mx-auto" defaultValue={[]}>
<For each={accordionItems}>
{(item) => (
<AccordionItem value={item.value}>
<AccordionTrigger>{item.title}</AccordionTrigger>
<AccordionContent class="text-tertiary">
{item.content}
</AccordionContent>
</AccordionItem>
)}
</For>
</Accordion>
);
}In SolidJS, use the <For> component instead of .map() for rendering lists.
This is the idiomatic SolidJS pattern for reactive list rendering.
Example components show practical usage patterns and can be used directly in your application or serve as templates for your own implementations.
Step 8: Use Components in Your App
Now you can import and use your example component in your SolidJS pages or components:
Update src/App.tsx (or your main component file):
import AccordionDemo from "@/components/shared/faq-accordion";
export default function App() {
return (
<div>
<AccordionDemo />
</div>
);
}Your Ocean UI component should now be working in your SolidJS application!
Understanding the Component Structure
It's important to understand the difference between base components and example components:
Base Components (components/ui/)
- Located in
components/ui/ - Wrap Ark UI primitives with Ocean UI styling
- Highly reusable and customizable
- Export multiple sub-components (e.g.,
Accordion,AccordionItem,AccordionTrigger,AccordionContent) - Use SolidJS patterns:
ParentComponent,splitProps, andclassprop
Example Components (components/shared/)
- Located in
components/shared/(orcomponents/demos/) - Use base components to create real-world examples
- Show practical usage patterns
- Can be used directly or as templates
- Use SolidJS patterns like
<For>for list rendering
›Troubleshooting
Components Not Styling Correctly
token.cssis properly imported in your main CSS file- Tailwind CSS is configured correctly in your Vite project
- All CSS variables from
token.cssare available - The
@import "tailwindcss"directive is included before importingtoken.css
Import Errors
- Verify all dependencies are installed (
@ark-ui/solid,lucide-solid) - Check that the
cnutility is insrc/lib/utils.ts - Ensure TypeScript path aliases are configured in
tsconfig.json(@/should resolve to yoursrc/directory) - Verify Vite is configured to resolve the
@/alias (checkvite.config.ts)
Animation Not Working
- Ensure
token.cssincludes the accordion animation keyframes - Check that Tailwind CSS animations are enabled
- Verify the animation classes are defined in your Tailwind config
- Make sure Tailwind CSS v4 is properly configured in your Vite project
Dark Mode Issues
- Ensure
token.cssincludes dark mode variables (inside.darkclass) - Verify your SolidJS app has dark mode configured (you may need to add a dark mode toggle)
- Check that the
darkclass is applied to your root element (typically<html>or<body>) - Consider using a library like
solid-js/storeor a theme provider for dark mode management
SolidJS-Specific Issues
- Ensure you're using
classinstead ofclassNamefor styling - Use
<For>component instead of.map()for list rendering - Remember to use
splitPropswhen destructuring props in components - Verify that components are using
ParentComponenttype for proper typing - Check that reactive primitives (signals, stores) are used correctly if needed
Next Steps
- Explore other base components from Ocean UI
- Customize the
token.cssto match your brand - Create your own example components
- Extend base components with additional functionality
- Check out component-specific documentation for advanced usage
- Learn more about SolidJS reactivity patterns and best practices
For more information about specific components, visit their individual documentation pages.