Next.js Installation
Prerequisites
- Node.js (v18 or higher)
- pnpm package manager (or npm/yarn)
- Basic knowledge of Next.js, React, and TypeScript
- A text editor or IDE
This guide uses Next.js with the App Router. The steps may vary slightly for Pages Router or other React frameworks.
Step 1: Create a Next.js Project
Start by creating a new Next.js project using the latest version:
pnpm create next-app@latest my-app --yes
cd my-app
pnpm devThis creates a new Next.js project with TypeScript and the App Router 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 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 React for icons. Install these core dependencies:
pnpm install @ark-ui/react lucide-react- @ark-ui/react: Provides accessible, headless UI primitives - lucide-react: 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 components/ui/accordion.tsx:
"use client";
import { cn } from "@/lib/utils";
import { Accordion as AccordionPrimitive } from "@ark-ui/react/accordion";
import { ChevronDownIcon } from "lucide-react";
import type { ComponentProps, ReactNode } from "react";
function Accordion({
className,
...props
}: ComponentProps<typeof AccordionPrimitive.Root>) {
return (
<AccordionPrimitive.Root className={cn("w-full", className)} {...props} />
);
}
function AccordionItem({
className,
...props
}: ComponentProps<typeof AccordionPrimitive.Item>) {
return (
<AccordionPrimitive.Item
className={cn("border-b border-border last:border-b-0", className)}
{...props}
/>
);
}
interface AccordionTriggerProps
extends ComponentProps<typeof AccordionPrimitive.ItemTrigger> {
/**
* Optional icon or content to display on the left side
*/
leftIcon?: ReactNode;
/**
* Optional icon or content to display on the right side
* If not provided, defaults to ChevronDownIcon
*/
rightIcon?: ReactNode;
}
function AccordionTrigger({
className,
children,
leftIcon,
rightIcon,
...props
}: AccordionTriggerProps) {
return (
<AccordionPrimitive.ItemTrigger
className={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",
className
)}
{...props}
>
{/* Left Icon Slot */}
{leftIcon}
{/* Title/Content */}
<span className="flex-1">{children}</span>
{/* Right Icon Slot */}
{rightIcon ? (
<span className="ml-auto">{rightIcon}</span>
) : (
<ChevronDownIcon className="accordion-chevron text-muted-foreground pointer-events-none size-4 shrink-0 transition-transform duration-200 ml-auto" />
)}
</AccordionPrimitive.ItemTrigger>
);
}
function AccordionContent({
className,
children,
...props
}: ComponentProps<typeof AccordionPrimitive.ItemContent>) {
return (
<AccordionPrimitive.ItemContent
className={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",
className
)}
{...props}
>
<div className={cn("pb-4", className)}>{children}</div>
</AccordionPrimitive.ItemContent>
);
}
export { Accordion, AccordionItem, AccordionTrigger, 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.
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
app/token.css with the following content:
Create app/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;
}
}
@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 components/shared/faq-accordion.tsx:
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
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 className="w-full max-w-lg mx-auto" defaultValue={[]}>
{accordionItems.map((item) => (
<AccordionItem key={item.value} value={item.value}>
<AccordionTrigger>{item.title}</AccordionTrigger>
<AccordionContent className="text-tertiary">
{item.content}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
);
}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 Next.js pages or components:
Update app/page.tsx:
import AccordionDemo from "@/components/shared/faq-accordion";
export default function Home() {
return (
<div>
<AccordionDemo />
</div>
);
}Your Ocean UI component should now be working in your Next.js 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)
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
›Troubleshooting
Components Not Styling Correctly
token.cssis properly imported inglobals.css- Tailwind CSS is configured correctly
- All CSS variables from
token.cssare available
Import Errors
- Verify all dependencies are installed (
@ark-ui/react,lucide-react) - Check that the
cnutility is inlib/utils.ts - Ensure TypeScript path aliases are configured (
@/should resolve to your project root)
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
Dark Mode Issues
- Ensure
token.cssincludes dark mode variables (inside.darkclass) - Verify your Next.js app has dark mode configured
- Check that the
darkclass is applied to your root element
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
For more information about specific components, visit their individual documentation pages.