Overview
Fataplus maintains a comprehensive UI component library that powers all frontend applications. The design system is built with modern web standards, optimized for multi-tenant environments, and seamlessly integrates with Figma designs.All components support both light and dark themes, are fully responsive, and follow WCAG accessibility guidelines.
Design System Structure
Technology Stack
Astro
Static site generation with islands architecture
React
Interactive components and client-side functionality
Tailwind CSS
Utility-first styling with custom design tokens
Component Architecture
Design Tokens
Color System
The Fataplus color system is based on brand identity with support for tenant customization::root {
/* Primary Colors - AgriTech Green */
--primary-green: hsl(142, 76%, 36%);
--primary-green-light: hsl(142, 76%, 46%);
--primary-green-dark: hsl(142, 76%, 26%);
/* Neutral Colors */
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
/* Card & Surface */
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--border: oklch(0.922 0 0);
/* Accent Colors */
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
/* Additional Brand Colors */
--secondary-color: #27ae60;
--accent-color: #3498db;
--dark-color: #2c3e50;
/* Design Properties */
--radius: 0.625rem;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
}
/* Dark Theme */
[data-theme="dark"] {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--border: oklch(1 0 0 / 10%);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
}
Typography
:root {
/* Font Families */
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
--font-mono: 'JetBrains Mono', 'Courier New', monospace;
/* Font Sizes */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 1.875rem; /* 30px */
--text-4xl: 2.25rem; /* 36px */
/* Line Heights */
--leading-tight: 1.25;
--leading-normal: 1.5;
--leading-relaxed: 1.75;
}
body {
font-family: var(--font-sans);
line-height: var(--leading-normal);
}
Base Components
Button Component
---
interface Props {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
size?: 'sm' | 'md' | 'lg';
href?: string;
type?: 'button' | 'submit' | 'reset';
class?: string;
}
const {
variant = 'primary',
size = 'md',
href,
type = 'button',
class: className = ''
} = Astro.props;
const baseStyles = 'inline-flex items-center justify-center font-medium transition-colors rounded-lg focus:outline-none focus:ring-2 focus:ring-offset-2';
const variants = {
primary: 'bg-primary text-white hover:bg-primary-dark focus:ring-primary',
secondary: 'bg-accent text-accent-foreground hover:bg-accent/80',
outline: 'border-2 border-primary text-primary hover:bg-primary hover:text-white',
ghost: 'hover:bg-accent hover:text-accent-foreground',
};
const sizes = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
};
const classes = `${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`;
---
{href ? (
<a href={href} class={classes}>
<slot />
</a>
) : (
<button type={type} class={classes}>
<slot />
</button>
)}
Card Component
---
interface Props {
title?: string;
description?: string;
hover?: boolean;
class?: string;
}
const {
title,
description,
hover = false,
class: className = ''
} = Astro.props;
const baseStyles = 'bg-card text-card-foreground rounded-lg border border-border shadow-sm';
const hoverStyles = hover ? 'transition-all hover:shadow-lg hover:border-primary/50' : '';
const classes = `${baseStyles} ${hoverStyles} ${className}`;
---
<div class={classes}>
{(title || description) && (
<div class="p-6 border-b border-border">
{title && <h3 class="text-xl font-semibold">{title}</h3>}
{description && <p class="text-muted-foreground mt-2">{description}</p>}
</div>
)}
<div class="p-6">
<slot />
</div>
</div>
Input Component
---
interface Props {
type?: 'text' | 'email' | 'password' | 'number' | 'tel';
label?: string;
placeholder?: string;
required?: boolean;
error?: string;
name: string;
class?: string;
}
const {
type = 'text',
label,
placeholder,
required = false,
error,
name,
class: className = ''
} = Astro.props;
const inputStyles = `
w-full px-4 py-2 rounded-lg border
${error ? 'border-red-500' : 'border-border'}
bg-background text-foreground
focus:outline-none focus:ring-2 focus:ring-primary
placeholder:text-muted-foreground
transition-colors
${className}
`;
---
<div class="space-y-2">
{label && (
<label for={name} class="block text-sm font-medium text-foreground">
{label}
{required && <span class="text-red-500 ml-1">*</span>}
</label>
)}
<input
type={type}
id={name}
name={name}
placeholder={placeholder}
required={required}
class={inputStyles}
/>
{error && (
<p class="text-sm text-red-500">{error}</p>
)}
</div>
Composite Components
Navigation Header
---
import Button from '@/components/ui/Button.astro';
interface NavLink {
label: string;
href: string;
active?: boolean;
}
const navLinks: NavLink[] = [
{ label: 'Home', href: '/' },
{ label: 'Projects', href: '/projects' },
{ label: 'Services', href: '/services' },
{ label: 'About', href: '/about' },
{ label: 'Contact', href: '/contact' },
];
---
<header class="fixed top-0 w-full z-50 bg-card/80 backdrop-blur-md border-b border-border">
<nav class="max-w-7xl mx-auto px-6 py-4">
<div class="flex items-center justify-between">
<!-- Logo -->
<a href="/" class="flex items-center gap-2 text-xl font-bold text-foreground hover:text-primary transition-colors">
<span class="text-2xl">🌱</span>
<span>Fataplus</span>
</a>
<!-- Navigation Links -->
<div class="hidden md:flex items-center gap-8">
{navLinks.map(link => (
<a
href={link.href}
class="text-foreground hover:text-primary font-medium transition-colors"
>
{link.label}
</a>
))}
</div>
<!-- CTA Button -->
<Button variant="primary" href="/quickstart">
Get Started
</Button>
</div>
</nav>
</header>
Project Card
---
import Card from '@/components/ui/Card.astro';
import Button from '@/components/ui/Button.astro';
interface Props {
project: {
id: string;
title: string;
description: string;
status: 'pending' | 'active' | 'completed';
category: string;
created_at: string;
};
}
const { project } = Astro.props;
const statusColors = {
pending: 'bg-yellow-100 text-yellow-800',
active: 'bg-green-100 text-green-800',
completed: 'bg-blue-100 text-blue-800',
};
---
<Card hover class="h-full flex flex-col">
<div class="flex items-start justify-between mb-4">
<div>
<h3 class="text-xl font-semibold mb-2">{project.title}</h3>
<p class="text-sm text-muted-foreground">{project.category}</p>
</div>
<span class={`px-3 py-1 rounded-full text-xs font-medium ${statusColors[project.status]}`}>
{project.status.charAt(0).toUpperCase() + project.status.slice(1)}
</span>
</div>
<p class="text-foreground mb-4 flex-grow">
{project.description}
</p>
<div class="flex items-center justify-between pt-4 border-t border-border">
<span class="text-sm text-muted-foreground">
{new Date(project.created_at).toLocaleDateString()}
</span>
<Button variant="outline" size="sm" href={`/projects/${project.id}`}>
View Details
</Button>
</div>
</Card>
Component Usage
Dashboard Layout
---
import Header from '@/components/navigation/Header.astro';
import ProjectCard from '@/components/projects/ProjectCard.astro';
import Card from '@/components/ui/Card.astro';
// Fetch projects from API
const response = await fetch('https://bknd.fata.plus/api/projects');
const projects = await response.json();
---
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Dashboard - Fataplus CRM</title>
</head>
<body>
<Header />
<main class="max-w-7xl mx-auto px-6 py-8 mt-20">
<h1 class="text-4xl font-bold mb-8">Dashboard</h1>
<!-- Stats Cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<Card>
<div class="text-center">
<p class="text-3xl font-bold text-primary">{projects.length}</p>
<p class="text-muted-foreground">Total Projects</p>
</div>
</Card>
<Card>
<div class="text-center">
<p class="text-3xl font-bold text-green-600">
{projects.filter(p => p.status === 'active').length}
</p>
<p class="text-muted-foreground">Active Projects</p>
</div>
</Card>
<Card>
<div class="text-center">
<p class="text-3xl font-bold text-blue-600">
{projects.filter(p => p.status === 'completed').length}
</p>
<p class="text-muted-foreground">Completed</p>
</div>
</Card>
</div>
<!-- Project Grid -->
<h2 class="text-2xl font-bold mb-6">Your Projects</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{projects.map(project => (
<ProjectCard project={project} />
))}
</div>
</main>
</body>
</html>
Responsive Design
All components are built with mobile-first responsive design:/* Mobile First Approach */
/* Default: Mobile (< 640px) */
/* Small tablets */
@media (min-width: 640px) { /* sm */ }
/* Tablets */
@media (min-width: 768px) { /* md */ }
/* Small laptops */
@media (min-width: 1024px) { /* lg */ }
/* Desktops */
@media (min-width: 1280px) { /* xl */ }
/* Large screens */
@media (min-width: 1536px) { /* 2xl */ }
Accessibility
ARIA Attributes
All interactive components include proper ARIA attributes:<button
type="button"
aria-label="Close dialog"
aria-pressed="false"
class="..."
>
<span aria-hidden="true">×</span>
</button>
Best Practices
Component Reusability
Build small, focused components that can be composed into larger patterns
Design Tokens
Use CSS custom properties for all design values to support theming
Semantic HTML
Use appropriate HTML elements for better accessibility and SEO
Performance
Lazy load components and optimize images for fast page loads
Next Steps
Figma Integration
Learn how components sync with Figma designs
Brand Management
Customize components for tenant-specific branding