Skip to content
Back to skills

Ui Guidelines

ASecurity

Ant Design house style for one specific React/Next.js application — its design tokens, color palette, typography scale, 8px/12px/16px/24px spacing, animations, and copy-ready patterns for antd tables, forms in modals, selectable card grids, loading states, avatars, and status tags. Use when building or restyling a form, table, modal, card, list, or page layout in a codebase that already standardizes on Ant Design and the new component must match the existing design system. It is not a general...

  • 7 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added October 14, 2026
ai-agentstypescriptgoreactnextjsnodeapi

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Pro scans all 6 files and shows the line behind each finding

Scanned October 14, 2026

npx -y skills add pfangueiro/claude-code-agents --skill ui-guidelines --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ui Guidelines?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Ui Guidelines
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/pfangueiro-ui-guidelines/badge)](https://www.skillsdirectory.com/skills/pfangueiro-ui-guidelines)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: ui-guidelines
description: Ant Design house style for one specific React/Next.js application — its design tokens, color palette, typography scale, 8px/12px/16px/24px spacing, animations, and copy-ready patterns for antd tables, forms in modals, selectable card grids, loading states, avatars, and status tags. Use when building or restyling a form, table, modal, card, list, or page layout in a codebase that already standardizes on Ant Design and the new component must match the existing design system. It is not a general UI or accessibility reference, does not apply to Tailwind, Material UI, Chakra, or plain-CSS projects, and carries no chart or data-visualization guidance — charts, graphs, dashboards, and palette choice for data viz are out of scope.
---

# UI Guidelines for Application

## Overview

This skill provides UI/UX guidelines for building components in a React/Next.js application using Ant Design and consistent design tokens. It ensures all new components match the existing design system with proper colors, spacing, typography, and interaction patterns.

## When to Use This Skill

Trigger this skill when:
- **Creating UI components**: forms, tables, modals, cards, lists
- **Adding new features** that require UI elements
- **Implementing loading states** or animations
- **Styling components** to match the design system
- **User asks to build/create/add** any visual component

## Out of Scope — Stop and Do Something Else

If the work you are about to do is any of the following, **stop using this skill** and take the redirect instead. Nothing below applies:

- **Charts, graphs, plots, or data-visualization palettes** (categorical / sequential / diverging series colors, sparklines, heatmaps, stat tiles, dashboard chart layout). This skill carries **no** chart guidance, and its brand palette is not a series palette — do not extrapolate one from it. Use a dedicated data-visualization skill or reference (`dataviz`, where available) instead.
- **A codebase that is not standardized on Ant Design** — Tailwind-only, Material UI, Chakra, or plain CSS. None of the patterns below transfer; follow that stack's own conventions.
- **A general accessibility or UX audit.** This is one application's house style, not an a11y reference.

Charts are out of scope regardless of what a reference file says — see the note on `styling-layout.md` under [Reference Files](#reference-files).

## Quick Start

### Step 1: Identify Component Type

Determine what you're building:
- **Drawer/Side Panel** → Read `references/component-patterns.md` (Drawer Patterns section) **[MANDATORY PATTERN]**
- **Data Table** → Read `references/codebase-patterns.md` (Tables section)
- **Form/Modal** → Read `references/codebase-patterns.md` (Modal Patterns section)
- **Card/Grid** → Read `references/codebase-patterns.md` (Card Patterns section)
- **Need colors/spacing** → Read `references/design-tokens.md`
- **Need animations/loading** → Read `references/animations.md`

### Step 2: Follow the Component Checklist

This is the **authoritative** checklist — use it both before you start and before you call the component done. Two reference files carry narrower, topic-scoped checklists (`styling-layout.md` for layout, `animations.md` for animation). They supplement this one; where either disagrees with this list, **this list wins**.

Every component must:
- [ ] Use Ant Design components as the base
- [ ] Apply consistent spacing (8px, 12px, 16px, 24px)
- [ ] Use theme tokens (`token.colorText`, `token.colorBgContainer`)
- [ ] Use `fontSize: 12` for tables (MANDATORY — see `references/component-patterns.md`)
- [ ] Include proper TypeScript types
- [ ] Handle loading states (Skeleton, Spin, or loading prop)
- [ ] Show feedback via `App.useApp()` — never the static `message` / `notification` / `Modal` imports (see [Feedback APIs](#feedback-apis))
- [ ] Include validation rules with clear messages on every form input
- [ ] Include proper error handling
- [ ] Support responsive design
- [ ] Match the existing patterns below

### Step 3: Apply Core Design Tokens

**Colors** (canonical source: `references/design-tokens.md` — if these ever disagree, that file wins):
- Brand Orange: `#F79400` (primary brand color)
- Product Owner: `#7C4DFF` (purple)
- Tech Owner: `#52c41a` (green)
- Error/Overdue: `#ff4d4f` (red)
- Always use `theme.useToken()` for dynamic colors

**Spacing:**
- Small gap: `8px`
- Medium gap: `12px`
- Standard padding: `16px`
- Section margin: `24px`

**Typography:**
- Table cells: `fontSize: 12`
- Secondary text: `fontSize: '11px'`
- Strong text: `<Text strong>`
- Secondary: `<Text type="secondary">`

## Feedback APIs

**MANDATORY: get `message`, `notification` and `modal` from `App.useApp()`. Never call the static `message.*` / `notification.*` / `Modal.confirm()` imports.**

This is not a style preference — it is forced by the `ConfigProvider` theming that the rest of this skill mandates. Ant Design's own docs state it plainly:

> Static methods like message, notification, and Modal.confirm do not inherit context from `ConfigProvider` because they are rendered to independent DOM nodes. To resolve style issues, use hooks like `message.useMessage`, `notification.useNotification`, `Modal.useModal`, or the `App.useApp` hook.
> — [antd FAQ](https://ant.design/docs/react/faq)

> we recommend to use top level registration instead of `notification` static method, because static method cannot consume context, and ConfigProvider data will not work.
> — [antd notification docs](https://ant.design/components/notification)

Measured on antd 5.29.3: inside `<ConfigProvider prefixCls="zzz" theme={{ token: { colorPrimary: '#F79400' } }}>`, a static `message.success()` rendered its holder with the **`ant-`** prefix and the **default** theme hash, while `App.useApp().message.success()` rendered it with the configured **`zzz-`** prefix and a *different*, configured hash. The static call silently ignored both the prefix and the brand token. (Reproduce it by diffing the two holders' class names — the exact hash strings are build-specific, so compare them against each other rather than against any literal quoted here.)

### Setup (once, at the app root)

`App` must sit **inside** `ConfigProvider` so it inherits the theme:

```tsx
import { ConfigProvider, App } from "antd";

export default function Providers({ children }) {
  return (
    <ConfigProvider theme={{ token: { colorPrimary: "#F79400" } }}>
      <App>{children}</App>
    </ConfigProvider>
  );
}
```

### Usage (every component)

```tsx
import { App, Button } from "antd";

export default function MyComponent() {
  // Call the hook at the top of the component...
  const { message, notification, modal } = App.useApp();

  // ...but FIRE the feedback from an event handler or an effect, never during render.
  // antd warns ("You are calling notice in render") and the notice is dropped.
  const handleSave = async () => {
    try {
      await save();
      message.success("Saved");
    } catch (err) {
      notification.error({ message: "Save failed", description: String(err) });
    }
  };

  const handleDelete = () => {
    modal.confirm({ title: "Are you sure?", onOk: doDelete });
  };

  return (
    <>
      <Button onClick={handleSave}>Save</Button>
      <Button danger onClick={handleDelete}>Delete</Button>
    </>
  );
}
```

| Don't (static — ignores `ConfigProvider`) | Do (`App.useApp()`) |
|---|---|
| `import { message } from "antd"; message.success(...)` | `const { message } = App.useApp(); message.success(...)` |
| `import { notification } from "antd"; notification.error(...)` | `const { notification } = App.useApp(); notification.error(...)` |
| `Modal.confirm({...})` | `const { modal } = App.useApp(); modal.confirm({...})` |

`App.useApp()` is a hook — call it at the top of the component, not inside a callback. For feedback fired outside React (an axios interceptor, a plain util), lift the call into a component or pass the instance in; do not fall back to the static import.

**Note:** `<Modal>` used as a *component* (Pattern 2 below) is unaffected — it renders inside the tree and inherits context normally. Only the static `Modal.confirm()` / `Modal.info()` family is affected.

## Core Patterns

### Pattern 1: Data Tables

All data tables should follow this structure:

```tsx
import { Table, Input, Select, ConfigProvider, theme } from "antd";
import { SearchOutlined } from "@ant-design/icons";

export default function DataTable() {
  const { token } = theme.useToken();
  
  return (
    <div style={{ height: "100%", display: "flex", flexDirection: "column" }}>
      {/* Filters row */}
      <div style={{ marginBottom: 16, display: "flex", gap: "8px" }}>
        <Input placeholder="Search..." prefix={<SearchOutlined />} style={{ flex: 1 }} allowClear />
        <Select style={{ flex: 1 }} placeholder="Filter" allowClear />
      </div>

      {/* Table with custom theme */}
      <ConfigProvider theme={{
        components: { Table: { headerBg: token.colorBgContainer, fontSize: 12 } }
      }}>
        <Table
          dataSource={data}
          loading={isLoading}
          rowKey="id"
          size="small"
          pagination={false}
          scroll={{ y: 'calc(100vh - 220px)', x: 'max-content' }}
        />
      </ConfigProvider>
    </div>
  );
}
```

**Key details:**
- Use `ConfigProvider` for table theme customization
- Set `fontSize: 12` for compact display
- Use `scroll={{ y: 'calc(100vh - 220px)' }}` for proper scrolling
- Always include `rowKey="id"`
- Set `size="small"` for compact tables

### Pattern 2: Forms in Modals

Standard form modal pattern:

```tsx
import { Modal, Form, Input, Button, App } from "antd";

export default function AddModal({ isOpen, onClose, onSuccess }) {
  // MANDATORY: not the static `message` import — it ignores ConfigProvider.
  // See "Feedback APIs" above.
  const { message } = App.useApp();
  const [form] = Form.useForm();
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleSubmit = async () => {
    try {
      const values = await form.validateFields();
      setIsSubmitting(true);
      await api.post('/endpoint', values);
      message.success("Created successfully");
      form.resetFields();
      onSuccess();
      onClose();
    } catch (error) {
      message.error(error.response?.data?.error || "Failed to create");
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <Modal title="Add Item" open={isOpen} onCancel={onClose} footer={null} maskClosable={true}>
      <Form form={form} layout="vertical" onFinish={handleSubmit}>
        <Form.Item name="name" label="Name" rules={[{ required: true, message: "Please enter name" }]}>
          <Input placeholder="Enter name" />
        </Form.Item>
        
        <Form.Item style={{ marginBottom: 0, marginTop: 16, textAlign: "right" }}>
          <Button onClick={onClose} style={{ marginRight: 8 }}>Cancel</Button>
          <Button htmlType="submit" loading={isSubmitting}>Create</Button>
        </Form.Item>
      </Form>
    </Modal>
  );
}
```

**Key details:**
- Use `layout="vertical"` for labels above inputs
- Include validation rules with clear messages
- Reset form on success: `form.resetFields()`
- Show feedback via `const { message } = App.useApp()` — the static `message` import ignores `ConfigProvider` ([Feedback APIs](#feedback-apis))
- Set `maskClosable={true}` for better UX

### Pattern 3: Selectable Card Grids

Horizontal scrolling cards with selection:

```tsx
import { theme, Typography, Avatar } from "antd";

const { Text } = Typography;
const { token } = theme.useToken();

<div style={{
  display: "flex",
  gap: "12px",
  overflowX: "auto",
  paddingBottom: "4px"
}}>
  {items.map((item) => {
    const isSelected = selectedId === item.id;
    return (
      <div
        key={item.id}
        onClick={() => setSelectedId(isSelected ? null : item.id)}
        style={{
          flex: 1,
          minWidth: '200px',
          maxWidth: '280px',
          padding: '12px',
          cursor: 'pointer',
          borderRadius: '8px',
          border: isSelected ? '1px solid #F79400' : `1px solid ${token.colorBorder}`,
          backgroundColor: isSelected ? token.colorFillSecondary : token.colorBgContainer,
          boxShadow: '0 1px 2px rgba(0, 0, 0, 0.04)',
        }}
      >
        <Text strong style={{ fontSize: '14px' }}>{item.name}</Text>
        {/* Additional content */}
      </div>
    );
  })}
</div>
```

**Key details:**
- Selected state: border with brand primary color
- Subtle shadow: `0 1px 2px rgba(0, 0, 0, 0.04)`
- Border radius: `8px`
- Use `token.colorBorder` for unselected state

### Pattern 4: Loading States

Always show loading feedback:

```tsx
// Table loading
<Table loading={isLoading} dataSource={data} />

// Card loading skeleton
{data === undefined ? (
  <Card loading style={{ minWidth: '200px' }} />
) : (
  <Card>{content}</Card>
)}

// Button loading
<Button htmlType="submit" loading={isSubmitting}>Submit</Button>

// Page content skeleton
<Skeleton active paragraph={{ rows: 4 }} />
```

### Pattern 5: Avatars with Fallbacks

```tsx
{user.profilePic ? (
  <Avatar size={24} src={getCachedAvatarUrl(user.profilePic)} />
) : (
  <Avatar size={24} style={{ backgroundColor: getAvatarColor(user.name) }}>
    {user.name.charAt(0).toUpperCase()}
  </Avatar>
)}
```

## Styling Approach

### Priority Order
1. **Ant Design props** first (type, size, danger, etc.)
2. **Theme tokens** for colors (`token.colorBgContainer`)
3. **Inline styles** for layout and spacing
4. **Tailwind** rarely (only for utility classes)

### DO's ✅
- Use Ant Design components as base
- Use `theme.useToken()` for colors
- Apply consistent spacing (8px, 12px, 16px, 24px)
- Include loading states everywhere
- Show user feedback with messages
- Handle errors gracefully
- Use TypeScript types

### DON'Ts ❌
- Don't use CSS-in-JS libraries
- Don't create custom CSS files
- Don't hardcode a color that has a theme token (`token.colorText`, `token.colorBorder`, `token.colorBgContainer`) — the brand palette in Step 3 is the documented exception and is written literally, as the patterns above show
- Don't skip error handling
- Don't ignore responsive design
- Don't use excessive shadows

## Common Recipes

### Status Tag
```tsx
<Tag color={status.color || "#d9d9d9"} style={{ borderRadius: "8px", fontSize: 12 }}>
  {status.name || "Not Set"}
</Tag>
```

### Clickable Text
```tsx
<Text strong style={{ cursor: 'pointer', fontSize: 12 }} onClick={() => router.push(`/path/${id}`)}>
  {title}
</Text>
```

### Overdue Indicator
```tsx
{isOverdue && (
  <Tooltip title={`Overdue since ${date}`}>
    <div style={{ width: '6px', height: '6px', borderRadius: '50%', backgroundColor: '#ff4d4f' }} />
  </Tooltip>
)}
```

### Select with Item Counts
```tsx
<Select>
  {items.map(item => (
    <Option key={item.id} value={item.id}>
      <div style={{ display: 'flex', justifyContent: 'space-between' }}>
        <span>{item.name}</span>
        <Text type="secondary" style={{ fontSize: '11px' }}>{item.count} items</Text>
      </div>
    </Option>
  ))}
</Select>
```

## Reference Files

Read these files for detailed information:

1. **[design-tokens.md](references/design-tokens.md)** - Colors, spacing, typography system. **Canonical for every color value.**
2. **[codebase-patterns.md](references/codebase-patterns.md)** - Real patterns from existing code
3. **[component-patterns.md](references/component-patterns.md)** - Ant Design component standards
4. **[styling-layout.md](references/styling-layout.md)** - Layout patterns and responsive design. Written Tailwind-first, so it reads against the grain of this file's Ant-Design-first mandates; its known conflicts (spacing grid, CSS files, inline styles, z-index, breakpoints, router, brand primary, charts) are reconciled inline in that file. Where anything there still disagrees with this file, **this file wins**.
5. **[animations.md](references/animations.md)** - Loading indicators and transitions. Drawer/modal behaviour is owned by `component-patterns.md`; that file wins on any drawer conflict.

## Typography Scale

```tsx
// Strong text (table headers, card titles)
<Text strong style={{ fontSize: '12px' }}>Title</Text>

// Regular text (table cells, body)
<Text style={{ fontSize: '12px' }}>Content</Text>

// Secondary text (subtitles, metadata)
<Text type="secondary" style={{ fontSize: '11px' }}>Metadata</Text>

// Tertiary text (additional info)
<Text style={{ fontSize: '11px', color: token.colorTextTertiary }}>Info</Text>
```

## Page Layout

Standard full-height page layout:

```tsx
export default function Page() {
  return (
    <div style={{ 
      height: "100%", 
      display: "flex", 
      flexDirection: "column",
      overflow: "hidden",
      padding: "24px 40px 24px 24px"
    }}>
      {/* Page content with proper scrolling */}
    </div>
  );
}
```

## Data Fetching

Use SWR for data fetching:

```tsx
import useSWR from "swr";
import { fetcher } from "@/lib/axios";

const { data, error, isLoading, mutate } = useSWR(
  '/api/endpoint',
  fetcher,
  {
    revalidateOnFocus: false,
    revalidateOnReconnect: false,
  }
);
```

## Before You Finish

Re-run the checklist in [Step 2](#step-2-follow-the-component-checklist) — there is no separate finishing list to diverge from it. If you also worked from `styling-layout.md` or `animations.md`, their topic checklists are supplements to Step 2, never replacements, and Step 2 wins on any conflict.

Files in this skill

  • SKILL.md17.2 KB
  • references/animations.md9.8 KB
  • references/codebase-patterns.md12.7 KB
  • references/component-patterns.md28.6 KB
  • references/design-tokens.md4.3 KB
  • references/styling-layout.md10.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.