MyApp

Getting Started

IntroductionInstallationPull Updates
Architecture

Setup

IDEAI AgentsMCP ServersEnvironment Variables

Workflow

Git WorkflowBuild & DeployTroubleshooting

Authentication

OverviewSetup & ConfigurationUsage & IntegrationTroubleshooting

Payments

OverviewSetup & ConfigurationUsage & IntegrationTroubleshooting

Supabase

OverviewSetup & ConfigurationTroubleshooting

Database

Database SetupPrisma ORMUsage & IntegrationMigrationsTroubleshooting

Storage

OverviewSetup & ConfigurationUsage & IntegrationTroubleshooting

Emails

OverviewSetup and ConfigurationUsage and IntegrationTroubleshooting

SEO

OverviewConfiguration & Best PracticesCustomization & Optimization

UI

OverviewSetup and ConfigurationThemingTroubleshooting
MyApp

Setup and Configuration

Learn how to add and configure UI components in your Plainform application

This guide covers adding new components, configuring Tailwind CSS 4, and customizing the UI system.

Adding New Components

Plainform's UI components are inspired by shadcn/ui but customized for this project. You can browse shadcn/ui's component library and adapt components to match Plainform's patterns.

Manual Component Creation

Create a new component in components/ui/:

components/ui/Badge.tsx
import * as React from 'react';
import { cn } from '@/lib/utils';

export interface IBadgeProps extends React.HTMLAttributes<HTMLDivElement> {}

export function Badge({ className, ...props }: IBadgeProps) {
  return (
    <div
      className={cn(
        'inline-flex items-center rounded-md px-2.5 py-0.5 text-xs font-semibold bg-brand text-brand-foreground',
        className
      )}
      {...props}
    />
  );
}

Note: Always prefix interfaces with I following Plainform's naming conventions.

Using Radix UI Primitives

For interactive components, use Radix UI. You can find components on shadcn/ui and adapt them:

Terminal
npm install @radix-ui/react-switch
components/ui/Switch.tsx
'use client';

import * as React from 'react';
import * as SwitchPrimitives from '@radix-ui/react-switch';
import { cn } from '@/lib/utils';

export const Switch = React.forwardRef<
  React.ElementRef<typeof SwitchPrimitives.Root>,
  React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
  <SwitchPrimitives.Root
    className={cn(
      'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-brand data-[state=unchecked]:bg-neutral',
      className
    )}
    {...props}
    ref={ref}
  >
    <SwitchPrimitives.Thumb
      className={cn(
        'pointer-events-none block h-4 w-4 rounded-full bg-surface shadow-lg transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0'
      )}
    />
  </SwitchPrimitives.Root>
));

Switch.displayName = SwitchPrimitives.Root.displayName;

Tailwind CSS 4 Configuration

Theme Customization

Tailwind CSS 4 uses the @theme directive in globals.css:

components/styles/globals.css
@theme {
  /* Spacing */
  --spacing-fd-container: 1224px;
  
  /* Border radius */
  --radius: 0.625rem;
  --radius-sm: calc(var(--radius) - 4px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
  
  /* Fonts */
  --font-poppins: var(--font-poppins);
  --font-roboto: var(--font-roboto);
  
  /* Custom colors */
  --color-surface: var(--surface);
  --color-foreground: var(--foreground);
  --color-brand: var(--brand);
  --color-neutral: var(--neutral);
}

Adding Custom Utilities

Create custom utilities in the @layer utilities block:

components/styles/globals.css
@layer utilities {
  .no-scrollbar::-webkit-scrollbar {
    display: none;
  }
  .no-scrollbar {
    -ms-overflow-style: none;
    scrollbar-width: none;
  }
  .navbar-border {
    border: 1px solid;
  }
}

Custom Animations

Define animations using @theme inline:

components/styles/globals.css
@theme inline {
  --animate-marquee: marquee var(--duration) infinite linear;
  --animate-shiny-text: shiny-text 8s infinite;

  @keyframes marquee {
    from {
      transform: translateX(0);
    }
    to {
      transform: translateX(calc(-100% - var(--gap)));
    }
  }

  @keyframes shiny-text {
    0%, 90%, 100% {
      background-position: calc(-100% - var(--shiny-width)) 0;
    }
    30%, 60% {
      background-position: calc(100% + var(--shiny-width)) 0;
    }
  }
}

Usage:

components/AnimatedText.tsx
<div className="animate-shiny-text">
  Animated text
</div>

Component Patterns

Compound Components

Create compound components for complex UI:

components/ui/Card.tsx
function Card({ className, ...props }: React.ComponentProps<'div'>) {
  return (
    <div
      className={cn('bg-neutral/40 rounded-md border p-6', className)}
      {...props}
    />
  );
}

function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
  return (
    <div className={cn('flex flex-col gap-1.5', className)} {...props} />
  );
}

function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
  return (
    <div className={cn('font-semibold', className)} {...props} />
  );
}

export { Card, CardHeader, CardTitle };

Usage:

app/page.tsx
<Card>
  <CardHeader>
    <CardTitle>Card Title</CardTitle>
  </CardHeader>
</Card>

Polymorphic Components

Use asChild prop for flexible composition:

components/ui/Button.tsx
import { Slot } from '@radix-ui/react-slot';

export interface ButtonProps {
  asChild?: boolean;
}

export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : 'button';
    return <Comp ref={ref} {...props} />;
  }
);

Usage:

app/page.tsx
<Button asChild>
  <Link href="/dashboard">Go to Dashboard</Link>
</Button>

Importing Components

Direct Imports

Import components directly (recommended):

app/page.tsx
import { Button } from '@/components/ui/Button';
import { Card } from '@/components/ui/Card';
import { Dialog } from '@/components/ui/Dialog';

Avoid Barrel Imports

Don't use barrel imports (prevents tree-shaking):

app/page.tsx
// ❌ BAD: Pulls entire module
import { Button, Card, Dialog } from '@/components/ui';

// ✅ GOOD: Direct imports enable tree-shaking
import { Button } from '@/components/ui/Button';
import { Card } from '@/components/ui/Card';
import { Dialog } from '@/components/ui/Dialog';

Form Components

Basic Form Setup

components/forms/ContactForm.tsx
'use client';

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import { Label } from '@/components/ui/Label';

const formSchema = z.object({
  email: z.string().email(),
  message: z.string().min(10)
});

type IFormData = z.infer<typeof formSchema>;

export function ContactForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<IFormData>({
    resolver: zodResolver(formSchema)
  });

  const onSubmit = async (data: IFormData) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
      <div>
        <Label htmlFor="email">Email</Label>
        <Input id="email" type="email" {...register('email')} />
        {errors.email && (
          <p className="text-sm text-red-500">{errors.email.message}</p>
        )}
      </div>
      
      <Button type="submit">Submit</Button>
    </form>
  );
}

Client vs Server Components

Client Components

Use 'use client' for interactive components:

components/ui/Dialog.tsx
'use client';

import * as DialogPrimitive from '@radix-ui/react-dialog';

export function Dialog({ ...props }) {
  return <DialogPrimitive.Root {...props} />;
}

Server Components

Keep non-interactive components as server components:

components/ui/Card.tsx
// No 'use client' directive needed

export function Card({ ...props }) {
  return <div {...props} />;
}

Next Steps

  • Theming - Customize colors and styles
  • Troubleshooting - Common issues and solutions

How is this guide ?

Last updated on

Overview

Learn about Plainform's UI system built with Tailwind CSS 4 and shadcn/ui components

Theming

Customize colors, fonts, and styles in your Plainform application

On this page

Adding New Components
Manual Component Creation
Using Radix UI Primitives
Tailwind CSS 4 Configuration
Theme Customization
Adding Custom Utilities
Custom Animations
Component Patterns
Compound Components
Polymorphic Components
Importing Components
Direct Imports
Avoid Barrel Imports
Form Components
Basic Form Setup
Client vs Server Components
Client Components
Server Components
Next Steps