Cách Mình Thiết Kế Giao Diện

Nền tảng design system, brand guidelines, và tiêu chuẩn UI components

Giao diện mà mỗi trang trông một kiểu thì user mất niềm tin ngay. Design system giúp mọi thứ nhìn đồng bộ -- từ button cho tới spacing, color cho tới typography -- để user dùng app mình mà không thấy "lạ lạ".

Ý tưởng cốt lõi: Nhất quán tạo niềm tin. Mọi trang, mọi component phải trông như cùng một sản phẩm.

Design System Của Mình

Mình build trên Tailwind CSS + Shadcn UI, customize cho brand:

┌─────────────────────────────────────────┐
│           Design Tokens                  │
│  Colors, Typography, Spacing, Shadows    │
├─────────────────────────────────────────┤
│           Base Components                │
│  Button, Input, Card, Dialog (Shadcn)    │
├─────────────────────────────────────────┤
│         Pattern Components               │
│  DataTable, FormSection, PageHeader      │
├─────────────────────────────────────────┤
│             Pages                        │
│  Dashboard, Settings, Results            │
└─────────────────────────────────────────┘

Design tokens là nền tảng, Shadcn cho base components, mình xây thêm pattern components phía trên, rồi ghép vào pages.

Colors

Bảng màu chính

MàuTailwind ClassDùng cho
Primarybg-primaryActions chính, links, focus states
Secondarybg-secondaryActions phụ, backgrounds
Accentbg-accentHighlights, notifications
Destructivebg-destructiveDelete, error states

Semantic Colors

Mục đíchLight ModeDark Mode
Backgroundbg-backgrounddark:bg-background
Foregroundtext-foregrounddark:text-foreground
Mutedbg-muteddark:bg-muted
Borderborder-borderdark:border-border

Chọn màu nào cho trường hợp nào

Tình huốngMàu
Button action chínhbg-primary
Button phụ/cancelbg-secondary
Action nguy hiểmbg-destructive
Thành côngtext-green-600
Cảnh báotext-yellow-600
Info/neutraltext-blue-600

Nguyên tắc đơn giản: primary color chỉ dùng cho main actions thôi. Phần lớn UI nên neutral -- dùng color có chiến lược chứ không phải cái gì cũng tô màu.

Typography

Font Stack

/* System fonts mặc định, ưu tiên performance */
font-family: ui-sans-serif, system-ui, sans-serif;

/* Monospace cho code */
font-family: ui-monospace, monospace;

Text Sizes

ElementTailwindSize
Tiêu đề trangtext-3xl font-bold30px
Heading sectiontext-2xl font-semibold24px
Tiêu đề cardtext-lg font-medium18px
Body texttext-base16px
Text nhỏtext-sm14px
Captiontext-xs text-muted-foreground12px

Spacing

Mình dùng thang spacing của Tailwind, giữ nhất quán xuyên suốt:

Trường hợpSpacingTailwind
Giữa các form fields16pxspace-y-4
Giữa các sections24pxspace-y-6
Card padding24pxp-6
Button padding16px horizontalpx-4 py-2
Page margins32pxp-8

Quan trọng là pick một convention rồi giữ xuyên suốt. Card padding là p-6 thì tất cả cards đều p-6 -- đừng trang này p-4 trang kia p-8.

Component Patterns

Buttons

// Action chính
<Button>Save Changes</Button>

// Action phụ
<Button variant="secondary">Cancel</Button>

// Action nguy hiểm
<Button variant="destructive">Delete</Button>

// Action ghost/subtle
<Button variant="ghost">Learn More</Button>

// Với loading state
<Button disabled>
  <Loader2 className="mr-2 h-4 w-4 animate-spin" />
  Saving...
</Button>

Forms

<form className="space-y-4">
  <div className="space-y-2">
    <Label htmlFor="name">Name</Label>
    <Input id="name" placeholder="Enter name" />
  </div>

  <div className="space-y-2">
    <Label htmlFor="email">Email</Label>
    <Input id="email" type="email" />
    <p className="text-sm text-muted-foreground">
      Mình sẽ không chia sẻ email của bạn.
    </p>
  </div>

  <Button type="submit">Submit</Button>
</form>

Cards

<Card>
  <CardHeader>
    <CardTitle>Card Title</CardTitle>
    <CardDescription>Mô tả ngắn</CardDescription>
  </CardHeader>
  <CardContent>
    {/* Nội dung chính */}
  </CardContent>
  <CardFooter>
    <Button>Action</Button>
  </CardFooter>
</Card>

Data Tables

<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Status</TableHead>
      <TableHead className="text-right">Actions</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>Item name</TableCell>
      <TableCell>
        <Badge variant="success">Active</Badge>
      </TableCell>
      <TableCell className="text-right">
        <Button variant="ghost" size="sm">Edit</Button>
      </TableCell>
    </TableRow>
  </TableBody>
</Table>

Layout Patterns

Cấu trúc trang

<div className="container mx-auto p-8">
  {/* Header trang */}
  <div className="mb-8">
    <h1 className="text-3xl font-bold">Page Title</h1>
    <p className="text-muted-foreground">Mô tả trang</p>
  </div>

  {/* Nội dung chính */}
  <div className="grid gap-6">
    {/* Các sections */}
  </div>
</div>

Dashboard Grid

<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
  <Card>{/* Metric 1 */}</Card>
  <Card>{/* Metric 2 */}</Card>
  <Card>{/* Metric 3 */}</Card>
</div>

Những Lỗi Hay Gặp

Spacing lung tung là lỗi phổ biến nhất. Trang này p-4, trang kia p-6, chỗ thì p-8 -- user nhìn vào thấy "lộn xộn" mà không biết tại sao. Cách sửa đơn giản: pick convention rồi giữ. Card padding p-6, section gaps space-y-6, xong.

Tương tự với color -- dùng quá nhiều màu, cái gì cũng highlight thì cuối cùng chẳng cái nào nổi bật cả. Primary color chỉ cho main actions, còn lại neutral.

Một lỗi nữa mà ai cũng mắc: không có loading state. User click button, không thấy gì xảy ra, click tiếp, click tiếp -- tạo 5 request cùng lúc. Luôn disable button + show spinner khi đang xử lý async.

Checklist

Đang ổn nếu:

  • Tất cả buttons cùng size và style
  • Spacing đều đặn giữa các elements
  • Màu dùng có mục đích, không tô lung tung
  • Loading states hiển thị đúng chỗ
  • Error messages rõ ràng
  • Dark mode không bể layout

Cần sửa nếu:

  • Mỗi trang button trông khác nhau
  • Spacing random, không theo quy tắc
  • Quá nhiều thứ "nổi bật", user không biết nhìn đâu
  • Không phân biệt được cái gì click được
  • Form submit lỗi mà user không biết lỗi gì

Tham Khảo Nhanh

ElementPattern
Primary button<Button>
Secondary button<Button variant="secondary">
Destructive action<Button variant="destructive">
Form spacingspace-y-4
Section spacingspace-y-6
Card paddingp-6
Page marginsp-8