Design System Guidelines
คู่มืออ้างอิงสำหรับนักออกแบบและนักพัฒนาเว็บไซต์ Codemotive — ครอบคลุม สี, ตัวอักษร, Spacing, Components, Layout และแนวทางการเขียนโค้ด เพื่อให้งานทุกชิ้นมีความสอดคล้องกัน
Colors
Codemotive ใช้ระบบสีที่เรียบง่าย — Black สำหรับ primary, Orange (#ff7a52) สำหรับ accent, และ Gray scale สำหรับ text และ surface ต่างๆ
Brand Black
Primary text, backgrounds, CTAs
Brand Orange
Accent, hover states, highlights
White
Backgrounds, cards, reversed text
Gray 50
Section backgrounds, zebra rows
Gray 100
Code backgrounds, subtle fills
Gray 400
Placeholder, secondary icons
Gray 500
Secondary text, captions
Gray 600
Body text, descriptions
Gray 900 (Dark BG)
Footer background, dark sections
Usage Rules
- ใช้ #141414 เป็นสีหลักสำหรับ CTA buttons, headings, และ footer background
- ใช้ #ff7a52 สำหรับ hover state ของ links/buttons, accents, และ highlight elements
- ใช้ gray-600 (#4b5563) สำหรับ body text ทั่วไป
- ใช้ gray-400–500 สำหรับ placeholder, captions, และ secondary info
- ห้ามใช้สีอื่นนอกจากในตารางนี้โดยไม่มีเหตุผล — ให้ยึด design token เสมอ
Typography
ใช้ 2 ฟอนต์หลัก: IBM Plex Sans Thai สำหรับ UI ทั้งหมด, และ Bai Jamjureeสำหรับ blog content body (เฉพาะใน .blog-content class)
IBM Plex Sans Thai
สวัสดีครับ — ข้อความภาษาไทย — ABCDEFGHIJKLMNOPQRSTUVWXYZ — 0123456789
weights: 300, 400, 500, 600, 700
Bai Jamjuree
สวัสดีครับ — ข้อความในบทความ — The quick brown fox
weight: 400 only — class: .blog-content
| Label | Size | Weight | Line Height | Usage |
|---|---|---|---|---|
| Display / H1 | text-5xl / 3rem | font-bold (700) | leading-[1.1] | Page hero headings |
| H2 Section | text-4xl / 2.25rem | font-bold (700) | leading-tight | Section headings |
| H3 Card | text-2xl / 1.5rem | font-semibold (600) | leading-snug | Card titles, sub-sections |
| H4 Label | text-xl / 1.25rem | font-semibold (600) | leading-normal | Sub-labels, table headers |
| Body Large | text-lg / 1.125rem | font-normal (400) | leading-relaxed (1.625) | Lead paragraphs, descriptions |
| Body Base | text-base / 1rem | font-normal (400) | leading-relaxed | General body copy |
| Small / Caption | text-sm / 0.875rem | font-medium (500) | leading-normal | Labels, badges, captions |
| XSmall / Meta | text-xs / 0.75rem | font-medium (500) | leading-normal | Tags, timestamps, metadata |
Live Examples
Display Heading — รับทำเว็บไซต์
Section Heading — บริการของเรา
Card Title — Landing Page
Body Large — เว็บไซต์คือสินทรัพย์บนโลกออนไลน์ที่เราเป็นเจ้าของ 100%
Caption / Label — เริ่มต้นเพียง 12,000 บาท
Spacing
ใช้ Tailwind spacing scale (1 unit = 4px) เป็นมาตรฐาน — ตาราง นี้แสดง tokens ที่ใช้บ่อยในโปรเจกต์
| Token | px Value | Usage |
|---|---|---|
| 4 | 16px | Minimum internal padding, gaps between tight elements |
| 6 | 24px | Default horizontal padding (mobile), card inner padding |
| 8 | 32px | Section vertical spacing, button padding |
| 10 | 40px | Component gap, form spacing |
| 12 | 48px | Large gaps between related sections |
| 16 | 64px | Default desktop page section padding |
| 24 | 96px | Hero padding top/bottom (mobile) |
| lg:px-12 | 48px | Desktop horizontal container padding |
Container Pattern
<section className="max-w-360 mx-auto px-6 lg:px-12 py-16 lg:py-24">
{/* page content */}
</section>max-width: 1440px, horizontal padding: 24px mobile / 48px desktop, vertical padding: 64px mobile / 96px desktop
Border Radius
ใช้ rounded corners แบบ generous เพื่อให้ดูทันสมัยและ friendly
rounded-lg
8px
Small cards, badges, inputs
rounded-xl
12px
Buttons, medium cards
rounded-2xl
16px
Large cards, panels
rounded-3xl
24px
Image thumbnails, hero images
rounded-full
9999px
Avatars, pill badges
Shadows
ใช้ Tailwind default shadow scale — ไม่มี custom shadow tokens
shadow-sm
Subtle card lift
shadow-md
Default card shadow, button shadow
shadow-lg
Modals, dropdowns, floating panels
shadow-xl
Hero elements, large popups
Layout & Grid
Codemotive ใช้ CSS Grid ผ่าน Tailwind — max-width container คือ 1440px เสมอ
Standard Page Container
ใช้กับทุก section ของหน้า
<section className="max-w-360 mx-auto px-6 lg:px-12 py-16 lg:py-24">
{/* content */}
</section>2-Column Hero Grid
Hero section pattern (text + image/visual)
<section className="max-w-360 mx-auto px-6 lg:px-12 py-16 lg:py-24
grid lg:grid-cols-2 gap-16 items-center">
<div>{/* left: text content */}</div>
<div>{/* right: visual */}</div>
</section>Card Grid (3 columns)
Blog cards, portfolio cards, service cards
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{items.map((item) => (
<Card key={item.id} {...item} />
))}
</div>4-Column Footer Grid
Footer links layout
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-10">
{/* columns */}
</div>12-Column Asymmetric Grid
About page: image (5 cols) + content (7 cols)
<div className="grid lg:grid-cols-12 gap-12 lg:gap-16 items-start">
<div className="lg:col-span-5">{/* image */}</div>
<div className="lg:col-span-7">{/* content */}</div>
</div>Icons
ใช้ lucide-react เป็นไลบรารีไอคอนหลัก — import เฉพาะที่ใช้งาน (tree-shakable)
Import Pattern
import { ArrowRight, Phone, Mail, Star, Check } from "lucide-react";
// Usage
<ArrowRight size={18} /> // default stroke
<Star size={20} className="text-[#ff7a52]" /> // colored
<Check size={16} strokeWidth={2.5} /> // custom stroke weightCommon Size Guide
| Size | Usage |
|---|---|
| size={14} | Inside badges, tags, small labels |
| size={16} | Inline with small text, metadata |
| size={18} | Default — buttons, list items |
| size={20} | Card headers, section labels |
| size={24} | Feature icons, standalone icons |
| size={32–48} | Hero icons, large decorative icons |
Code Style
TypeScript strict mode — functional components — server components by default
Component Template
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Page Title — Codemotive",
description: "...",
};
export default function ExamplePage() {
return (
<div className="animate-fade-in">
<section className="max-w-360 mx-auto px-6 lg:px-12 py-16 lg:py-24">
{/* content */}
</section>
</div>
);
}Client Component
"use client"; // ← ต้องการเมื่อใช้ useState/useEffect/events
import { useState } from "react";
export default function InteractiveWidget() {
const [isOpen, setIsOpen] = useState(false);
// ...
}Import Order
// 1. External packages
import type { Metadata } from "next";
import Link from "next/link";
import { ArrowRight } from "lucide-react";
// 2. Internal modules (@ alias)
import { services } from "@/data";
import { getMarkdownBlogPosts } from "@/lib/blog";
// 3. Components
import SectionBadge from "@/components/section-badge";
import CtaSection from "@/components/cta-section";TypeScript Rules
// ✅ Good — explicit return type for exports
export const getService = (slug: string): Service | undefined =>
services.find((s) => s.slug === slug);
// ✅ Good — unknown + narrow instead of any
const parseData = (raw: unknown): User => {
if (!isUser(raw)) throw new Error("Invalid");
return raw;
};
// ❌ Avoid
const bad = (data: any) => data.id; // no any!Key Rules Summary
Naming Conventions
แนวทางการตั้งชื่อ file, variable, type, และ constant ในโปรเจกต์
| Category | Convention | Example |
|---|---|---|
| Files & Folders | kebab-case | user-profile.tsx, auth-service.ts |
| Variables & Functions | camelCase | getUserData(), isLoading, blogPosts |
| Types & Interfaces | PascalCase | BlogPost, ServiceItem, PortfolioCard |
| Constants (true constants) | UPPER_SNAKE_CASE | MAX_RESULTS, BASE_URL |
| Constants (derived values) | camelCase | defaultTitle, primaryColor |
| Boolean variables | is/has/should/can prefix | isLoading, hasPermission, canEdit |
| React Components | PascalCase | BlogCard, SectionBadge, NavBar |
| Component Files | kebab-case | blog-card.tsx, section-badge.tsx |
| Route Segments | kebab-case | /services/landing-page-sale-page |
| CSS Classes | Tailwind utilities | No custom class names unless in globals.css |
Commit Message Format (Conventional Commits)
feat: add portfolio filter component
fix: resolve mobile nav overflow issue
chore: update dependencies
docs: add design system page
refactor: extract hero section into component
style: adjust spacing on services page
test: add unit tests for blog util functionsAnimation
ใช้ animation เพียงรูปแบบเดียว — fade-in จาก globals.css — ครอบที่ root element ของแต่ละ page
/* globals.css */
@keyframes fade-in {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-in {
animation: fade-in 0.5s ease-out;
}
/* Usage — always wrap page root div */
<div className="animate-fade-in">
{/* page content */}
</div>ห้ามเพิ่ม animation อื่นโดยไม่จำเป็น — transition-colors สำหรับ hover เท่านั้น
Section Badge
Component <SectionBadge> ใช้เป็น eyebrow label เหนือ heading ของ section
// src/components/section-badge.tsx
<SectionBadge>เปลี่ยนคนแปลกหน้าในเว็บ ให้เป็นลูกค้าประจำของคุณ</SectionBadge>
// Renders:
<div className="flex items-center gap-2 text-sm font-bold tracking-wider uppercase text-gray-500 mb-6">
<div className="w-1.5 h-1.5 bg-[#141414] rounded-full" />
{children}
</div>Images
แนวทางการใช้งานรูปภาพในโปรเจกต์
Aspect Ratios & Classes
| Hero / Full-width | h-[400px] lg:h-[600px] object-cover | About page profile image |
| Blog cover | aspect-video object-cover | Blog card thumbnails |
| Portfolio thumb | aspect-video object-cover rounded-2xl | Portfolio grid cards |
| Avatar / Logo | w-16 h-16 object-contain | Small logos, DBD badge |
Image Hosting
- Static assets:
/public/images/ - Media (photos, cloudinary):
res.cloudinary.com/cmtd/image/upload/... - Blog post images: ระบุใน frontmatter
img:และcoverImg:
Codemotive Design System
เอกสารนี้เป็น Internal Reference
อัปเดตเมื่อเพิ่ม component หรือ pattern ใหม่เข้าโปรเจกต์ ทุกการเปลี่ยนแปลง design token ควรสะท้อนในหน้านี้ด้วยเสมอ