Skip to content

Frontend Development

2 posts with the tag “Frontend Development”

Guide to Creating a Border Light Sweep Animation Effect

Guide to Creating a Border Light Sweep Animation Effect

Section titled “Guide to Creating a Border Light Sweep Animation Effect”

How do you build that important element users notice at a glance using pure CSS? It is actually not that hard. The trick is just taking a slightly roundabout path. In this article, I will walk you through how to build a border light sweep animation from scratch, and also share a few of the pitfalls we ran into while building HagiCode.

If you work on the frontend, you have probably had this experience before: a product manager walks over with that “this is definitely simple” expression and says, “Can we add some kind of special effect to this running task so users can spot it immediately?”

You say sure, we can just change the border color. Then they shake their head with that look that says, “You do not get it.” They reply, “That is not obvious enough. I want the kind of effect where light runs around the border, like in a sci-fi movie.”

At that point you might start wondering how to build it. Canvas? SVG? Or can CSS handle it on its own? After all, nobody wants to admit they do not know how.

In modern web applications, border light sweep animations are actually very common. They are mainly used in a few scenarios like these:

  • Status indicators: Marking tasks in progress or active items
  • Visual focus: Highlighting important content areas
  • Brand enhancement: Creating a sleek, modern, tech-forward visual style
  • Seasonal themes: Building a celebratory atmosphere for special occasions

We ran into exactly this requirement while building HagiCode. Users needed to see at a glance which sessions were running and which proposals were currently being processed. We tried several different approaches. Some paths were smoother, some were a bit more winding. In the end, we settled on a fairly mature implementation strategy.

The approach shared in this article comes from our hands-on experience in the HagiCode project. HagiCode is an AI-driven coding assistant project, and the interface makes extensive use of border light animations to indicate different runtime states. Examples include the running state of items in the session list, status transitions in the proposal flow diagram, and intensity indicators for throughput.

These effects are not especially complicated in principle, but we definitely ran into plenty of pitfalls while implementing them. If you want to see the real thing, you can visit our GitHub repository or head to the official website. In the end, what matters most is what actually works.

After analyzing the HagiCode codebase, we summarized several core implementation patterns below. Each one fits a different scenario, or in other words, each one exists for a reason.

1. Rotating glow with a conic gradient (most common)

Section titled “1. Rotating glow with a conic gradient (most common)”

This is the classic way to implement a border light sweep effect. The core idea is to use CSS conic-gradient to create a conic gradient, then rotate it continuously. Like a streetlight turning in the night, it just keeps circling.

Key elements:

  • Use the ::before pseudo-element to create the glow layer
  • Use conic-gradient to define the gradient color distribution
  • Use the ::after pseudo-element to mask the center area (optional)
  • Use @keyframes to implement the rotation animation

This works well for status indicators in list items. You create a thin glowing line on one side of the element instead of animating the entire border. Sometimes a little light is enough. You do not need to illuminate the whole world.

Key elements:

  • A thin absolutely positioned line element
  • Use box-shadow to create the glow effect
  • Use scale and opacity for a breathing animation

If you do not need the full sweep effect and just want a soft background glow, layering multiple box-shadow values is enough. Sometimes the simpler option is the better one.

This part is easy to overlook, but it is extremely important. Every animation should account for the prefers-reduced-motion media query and provide a static alternative for users who do not want animation. Not everyone enjoys constant motion, and respecting that preference matters.

Section titled “Option 1: Rotating conic-gradient border (recommended)”

This is the most complete implementation of the sweeping border light effect, and it is also the option we use most often in HagiCode. After all, if something works well, why replace it?

/* Parent container */
.glow-border-container {
position: relative;
overflow: hidden;
}
/* Rotating glow layer */
.glow-border-container::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: conic-gradient(
transparent 0deg,
rgba(59, 130, 246, 0.6) 60deg,
rgba(59, 130, 246, 0.3) 120deg,
rgba(59, 130, 246, 0.6) 180deg,
transparent 240deg
);
animation: border-rotate 3s linear infinite;
z-index: -1;
}
/* Mask layer (optional, for creating a hollow border effect) */
.glow-border-container::after {
content: '';
position: absolute;
inset: 2px;
background: inherit;
border-radius: inherit;
z-index: -1;
}
@keyframes border-rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

The principle behind this option is fairly simple: create a pseudo-element larger than the parent container, draw a conic gradient on it, and rotate it continuously. The parent container uses overflow: hidden, so only the light passing around the border remains visible. It is a bit like watching a streetlight through a window. You only ever see the small slice that passes by.

Option 2: Simplified rotating light border

Section titled “Option 2: Simplified rotating light border”

If you do not need the full effect, HagiCode also includes a lighter utility-class version. Sometimes the simpler approach really is better.

/* Rotating light border utility class */
.running-light-border {
position: absolute;
inset: -2px;
background: conic-gradient(
from 0deg,
transparent 0deg 270deg,
var(--theme-running-color) 270deg 360deg
);
border-radius: inherit;
animation: lightRayRotate 3s linear infinite;
will-change: transform;
z-index: 0;
}
@keyframes lightRayRotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* Accessibility support */
@media (prefers-reduced-motion: reduce) {
.running-light-border {
animation: none;
}
}

Notice the will-change: transform here. It tells the browser, “This element is going to keep changing,” so the browser can prepare some optimizations ahead of time and keep the animation smoother. Preparing in advance is usually better than scrambling at the last minute.

This is especially suitable for list-item status indicators, and it is exactly what the HagiCode session list uses. One thin line can still stand out among many items. That feels like a life lesson in its own way.

.side-glow {
position: relative;
isolation: isolate;
}
.side-glow::before {
content: '';
position: absolute;
left: 0;
top: 14px;
bottom: 14px;
width: 1px;
border-radius: 999px;
background: var(--theme-running-color);
box-shadow:
0 0 16px var(--theme-running-color),
0 0 28px var(--theme-running-color);
z-index: 1;
pointer-events: none;
animation: sidePulse 2.6s ease-in-out infinite;
}
.side-glow > * {
position: relative;
z-index: 2;
}
@keyframes sidePulse {
0%, 100% {
opacity: 0.55;
transform: scaleY(0.96);
}
50% {
opacity: 0.95;
transform: scaleY(1);
}
}

This uses isolation: isolate to create a new stacking context, then relies on z-index to control the display order of each layer. pointer-events: none is also essential. Otherwise the pseudo-element would block user clicks. Some things can look nice, but they still should not get in the way.

If your project uses React, you can wrap this logic in a component, especially the accessibility handling. Write it once, use it many times. That is the whole point.

import React from 'react';
import { useReducedMotion } from 'framer-motion';
import styles from './GlowBorder.module.css';
interface GlowBorderProps {
isActive: boolean;
children: React.ReactNode;
className?: string;
}
export const GlowBorder = React.memo<GlowBorderProps>(
({ isActive, children, className = '' }) => {
const prefersReducedMotion = useReducedMotion();
if (!isActive) {
return <div className={className}>{children}</div>;
}
if (prefersReducedMotion) {
return (
<div className={`${styles.glowStatic} ${className}`}>
{children}
</div>
);
}
return (
<div className={`${styles.glowAnimated} ${className}`}>
{children}
</div>
);
}
);

The matching CSS module:

GlowBorder.module.css
/* Animated version */
.glowAnimated {
position: relative;
overflow: hidden;
}
.glowAnimated::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: conic-gradient(
from 0deg,
transparent,
rgba(59, 130, 246, 0.6),
transparent,
rgba(59, 130, 246, 0.6),
transparent
);
animation: rotateGlow 3s linear infinite;
z-index: -1;
}
.glowAnimated::after {
content: '';
position: absolute;
inset: 2px;
background: inherit;
border-radius: inherit;
z-index: -1;
}
/* Static version (accessibility) */
.glowStatic {
position: relative;
border: 1px solid rgba(59, 130, 246, 0.5);
box-shadow: 0 0 15px rgba(59, 130, 246, 0.3);
}
@keyframes rotateGlow {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

The useReducedMotion hook from framer-motion automatically detects the user’s system preference. If the user has enabled reduced motion, it returns true, and the component shows the static version instead. Respecting the user’s preference matters more than forcing a flashy effect.

These are some of the lessons we learned while building HagiCode. You could also call them battle scars. Hopefully they help you avoid some detours.

CSS variables make multi-theme support especially convenient. Nobody wants to edit a pile of code every time the theme changes.

:root {
--glow-color-light: rgb(16, 185, 129);
--glow-color-dark: rgb(16, 185, 129);
--theme-glow-color: var(--glow-color-light);
}
html.dark {
--theme-glow-color: var(--glow-color-dark);
}
/* Usage */
.glow-effect {
background: var(--theme-glow-color);
box-shadow: 0 0 20px var(--theme-glow-color);
}

That way, switching themes only requires changing the class on the html element, and every animation color updates automatically. One codebase, two styles. That is exactly what we want.

Use will-change to hint the browser to optimize:

.animated-glow {
will-change: transform, opacity;
}

Tell the browser in advance, and it will help you optimize. A lot of things in life work better with a little preparation.

Avoid using complex box-shadows on large elements:

/* Not ideal - using a blurred shadow on a large element */
.large-card {
box-shadow: 0 0 50px rgba(0, 0, 0, 0.5);
}
/* Better - use a pseudo-element to limit the glowing area */
.large-card::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: 0 0 20px var(--glow-color);
pointer-events: none;
}

We tested this in HagiCode. Adding a blurry shadow directly to a large card dropped scrolling frame rates below 30fps. Switching to a pseudo-element brought things back to a steady 60fps. Users can absolutely feel that difference.

You really should not skip this. Some users find animation dizzying or distracting, and respecting their preferences is part of building a good product. Beautiful things do not need to be imposed on everyone.

CSS media query:

@media (prefers-reduced-motion: reduce) {
.glow-animation {
animation: none;
}
.glow-animation::before {
/* Provide a static fallback */
opacity: 1;
}
}

Detect user preference in React:

import { useReducedMotion } from 'framer-motion';
const Component = () => {
const prefersReducedMotion = useReducedMotion();
return (
<div className={prefersReducedMotion ? 'static-glow' : 'animated-glow'}>
Content
</div>
);
};

The Token throughput indicator in HagiCode shows different glow colors based on real-time throughput, and this is implemented dynamically. Different states should be expressed differently.

const colors = [
null, // Level 0 - no color
'#3b82f6', // Level 1 - Blue
'#34d399', // Level 2 - Emerald
'#facc15', // Level 3 - Yellow
'#fbbf24', // Level 4 - Amber
'#f97316', // Level 5 - Orange
'#22d3ee', // Level 6 - Cyan
'#d946ef', // Level 7 - Fuchsia
'#f43f5e', // Level 8 - Rose
];
const IntensityGlow = ({ intensity }) => {
const glowColor = colors[Math.min(intensity, colors.length - 1)];
return (
<div
className="glow-effect"
style={{
'--glow-color': glowColor,
opacity: 0.6 + (intensity * 0.08),
}}
/>
);
};

There are still a few details worth paying attention to, because by the time you discover these problems the hard way, it is already too late.

Things to watch out forExplanation
z-index managementThe glow layer should use an appropriate z-index so it does not interfere with content interaction
pointer-eventsThe glow pseudo-element should set pointer-events: none
Boundary overflowThe parent container needs overflow: hidden, or you need to adjust pseudo-element sizing
Performance impactComplex animations can hurt performance on mobile devices, so test carefully
Dark modeMake sure the glow color remains clearly visible on dark backgrounds
Theme switchingUse CSS variables so animation colors update correctly when the theme changes

Pseudo-elements can be a little hard to locate in developer tools, so you can temporarily add a border to check the position.

/* Temporarily show pseudo-element boundaries for debugging */
.glow-effect::before {
/* debug: border: 1px solid red; */
}

After you finish positioning it, remember to comment out or remove that line. Otherwise production can get awkward pretty quickly. Some things are better left in development.

Border light sweep animations are neither especially hard nor truly trivial. At the core, the formula is conic-gradient plus rotation, but if you want good performance, maintainability, and accessibility, there are still plenty of implementation details to handle carefully.

HagiCode hit a lot of these pitfalls and gradually distilled a set of best practices. That is just how projects go: you experiment, make mistakes, and improve one step at a time. If you are building something similar, I hope this article helps you avoid a few unnecessary detours.

Some things only become clear once you build them yourself.

Thank you for reading. If you found this article useful, feel free to like, bookmark, and share it. This content was created with AI-assisted collaboration, and the final version was reviewed and approved by the author.

Design.md: A Solution for Consistent AI-Driven Frontend UI Design

Design.md: A Solution for Consistent AI-Driven Frontend UI Design

Section titled “Design.md: A Solution for Consistent AI-Driven Frontend UI Design”

In the era of AI-assisted frontend development, how can you keep AI-generated UIs consistent? This article shares our hands-on experience building a design gallery site based on awesome-design-md, along with how to create a structured design.md to guide AI toward standardized UI design.

Anyone who has used AI to write frontend code has probably had a similar experience: ask AI to generate the same page several times, and each result comes out in a different style. Sometimes corners are rounded, sometimes they are sharp. Sometimes spacing is 8px, and other times it becomes 16px. Even the same button can look different across different conversations.

This is not an isolated issue. As AI-assisted development becomes more common, the lack of consistency in AI-generated frontend UI has become a widespread problem. Different AI assistants, different prompts, and even the same assistant across different conversations can produce dramatically different interface designs. That creates a huge maintenance cost during product iteration.

The root cause is actually simple: there is no authoritative design reference document. Traditional CSS stylesheets can tell developers “how to implement” something, but they cannot fully communicate “why it is designed this way” or “which design pattern should be used in which scenario.” For AI, a clear and structured description is even more important for understanding design conventions.

At the same time, the open-source community already offers some excellent resources. The VoltAgent/awesome-design-md project collects design system documentation from many well-known companies. Each directory contains a README.md, a DESIGN.md, and preview HTML. However, all of that is scattered across the upstream repository, making it hard to browse and compare quickly.

So, can we consolidate those resources into an easy-to-browse design gallery, while also distilling a structured design.md for AI to use?

The answer is yes. Next, let me walk through our approach.

The solution shared in this article comes from our hands-on experience in the HagiCode project. HagiCode is an AI-assisted development platform, and during development we ran into the same problem of inconsistent AI-generated UIs. To solve it, we built a design gallery site and created a standardized design.md. This article is a summary of that solution.

GitHub - HagiCode-org/site

First, take a look at the final homepage. It brings together the design gallery entry point, the site repository, the upstream repository, and background information about HagiCode in a single interface, making it easy for the team to establish a shared context before diving into specific entries.

Awesome Design MD Gallery homepage overview

Before writing code, let us break down the technical challenges behind this problem.

Source Content Management: How Do You Unify Scattered Design Resources?

Section titled “Source Content Management: How Do You Unify Scattered Design Resources?”

The upstream awesome-design-md repository contains a large number of design documents, but we needed a way to bring them into our own project.

Solution: use git submodule

awesome-design-md-site
└── vendor/awesome-design-md # Upstream resources (git submodule)

This gives us several benefits:

  • Version control: we can pin a specific upstream version
  • Offline builds: no need to request external APIs during the build
  • Content review: specific changes are visible in PRs

Data Normalization: How Do You Standardize Different Document Structures?

Section titled “Data Normalization: How Do You Standardize Different Document Structures?”

Different companies structure their design documents differently. Some are missing preview files, and some use inconsistent naming. We need to normalize them during the build process.

Solution: scan and generate normalized entries at build time

The core module is awesomeDesignCatalog.ts, responsible for:

  1. Scanning the vendor/awesome-design-md/design-md/* directory
  2. Validating whether each entry contains the required files (README.md, DESIGN.md, and at least one preview file)
  3. Extracting and rendering Markdown content into HTML
  4. Generating normalized entry data
src/lib/content/awesomeDesignCatalog.ts
export interface DesignEntry {
slug: string;
title: string;
summary: string;
readmeHtml: string;
designHtml: string;
previewLight?: string;
previewDark?: string;
searchText: string;
}
export async function scanSourceEntries() {
// Scan vendor/awesome-design-md/design-md/*
// Validate file completeness
// Generate normalized entries
}
export async function normalizeDesignEntry(dir: string) {
// Extract README.md and DESIGN.md
// Parse preview files
// Render Markdown to HTML
}

Static Site Architecture: How Do You Provide Dynamic Search While Staying Fully Static?

Section titled “Static Site Architecture: How Do You Provide Dynamic Search While Staying Fully Static?”

Since this is a design gallery, search is a must-have. But Astro is a static site generator, so how do you implement real-time search?

Solution: React island + URL query parameter sync

src/components/gallery/SearchToolbar.tsx
export function SearchToolbar() {
const [query, setQuery] = useState('');
// Sync with the URL
useEffect(() => {
const params = new URLSearchParams(window.location.search);
setQuery(params.get('q') || '');
}, []);
// Filter in real time
const filtered = entries.filter(entry =>
entry.searchText.includes(query)
);
return <input value={query} onChange={e => {
setQuery(e.target.value);
updateURL(e.target.value);
}} />;
}

The advantage of this approach is that it keeps the deployability of a static site intact, meaning it can be deployed to any static hosting service, while still delivering an instant filtering experience.

Design Documentation: How Do You Help AI Understand and Follow Design Standards?

Section titled “Design Documentation: How Do You Help AI Understand and Follow Design Standards?”

This is the core of the entire solution. We need to create a structured design.md that AI can understand and apply.

Solution: borrow the structure of ClickHouse DESIGN.md

ClickHouse’s DESIGN.md is an excellent reference. It includes:

  • Visual Theme & Atmosphere
  • Color Palette & Roles
  • Typography Rules
  • Component Stylings
  • Layout Principles
  • Depth & Elevation
  • Do’s and Don’ts
  • Responsive Behavior
  • Agent Prompt Guide

Our approach is: reuse the structure, rewrite the content. We keep the section structure of ClickHouse DESIGN.md, but replace the content with the actual design tokens and component conventions used in our own project.

Based on the analysis above, our solution consists of four core modules.

This is the foundation of the whole system, responsible for extracting and normalizing content from upstream resources.

src/lib/content/awesomeDesignCatalog.ts
export async function scanSourceEntries(): Promise<DesignEntry[]> {
const designDir = 'vendor/awesome-design-md/design-md';
const entries: DesignEntry[] = [];
for (const dir of await fs.readdir(designDir)) {
const entryPath = path.join(designDir, dir);
if (await isValidDesignEntry(entryPath)) {
const entry = await normalizeDesignEntry(entryPath);
entries.push(entry);
}
}
return entries;
}
async function isValidDesignEntry(dir: string): Promise<boolean> {
const requiredFiles = ['README.md', 'DESIGN.md'];
for (const file of requiredFiles) {
if (!(await fileExists(path.join(dir, file)))) {
return false;
}
}
return true;
}

The gallery interface includes three main parts:

Homepage: displays a card grid of all design entries, and each card includes:

  • Design entry title and summary
  • Preview image, if available
  • Quick-search highlighting

Detail page: aggregates the full information for a single design entry:

  • README document
  • DESIGN document
  • Preview with light/dark theme switching
  • Navigation to adjacent entries

Navigation: supports returning to the gallery and browsing adjacent entries

The homepage gallery uses a high-density card layout, flattening design.md entries from different sources into a unified visual framework so that teams can quickly compare brand styles, button patterns, and typographic rhythm.

Awesome Design MD Gallery design card grid

After opening a specific entry, the detail page places the design summary and live preview on the same page, reducing the cost of switching back and forth among documentation, previews, and source code.

Awesome Design MD Gallery design detail preview page

The search feature is based on client-side filtering, with state preserved through URL query parameters:

src/components/gallery/SearchToolbar.tsx
function SearchToolbar({ entries }: { entries: DesignEntry[] }) {
const [query, setQuery] = useState('');
const [results, setResults] = useState(entries);
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const initialQuery = params.get('q') || '';
setQuery(initialQuery);
filterEntries(initialQuery);
}, []);
const filterEntries = (searchQuery: string) => {
const filtered = entries.filter(entry =>
entry.searchText.toLowerCase().includes(searchQuery.toLowerCase())
);
setResults(filtered);
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setQuery(value);
filterEntries(value);
// Update the URL without triggering a page refresh
const newUrl = value
? `${window.location.pathname}?q=${encodeURIComponent(value)}`
: window.location.pathname;
window.history.replaceState({}, '', newUrl);
};
return (
<div className="search-toolbar">
<input
type="text"
value={query}
onChange={handleChange}
placeholder="Search design entries..."
/>
<span className="result-count">{results.length} results</span>
</div>
);
}

This is the core deliverable of the whole solution. We create a design.md in the project root with the following structure:

In addition to the raw design.md content consumed by AI, we also place both the README and DESIGN documents into the same reading interface, making it easier for people to proofread, copy snippets, and compare them against the preview results.

Awesome Design MD Gallery README and DESIGN document page

# Design Reference for [Project Name]
## 1. Visual Theme & Atmosphere
- Overall style description
- Design philosophy and principles
## 2. Color Palette & Roles
- Primary and supporting colors
- Semantic colors (`success`, `warning`, `error`)
- CSS variable definitions
## 3. Typography Rules
- Font families
- Type scale (`h1-h6`, `body`, `small`)
- Line height and font weight
## 4. Component Stylings
- Button style conventions
- Form component styles
- Card and container styles
## 5. Layout Principles
- Spacing system
- Grid and breakpoints
- Alignment principles
## 6. Depth & Elevation
- Shadow levels
- `z-index` conventions
## 7. Do's and Don'ts
- Common mistakes and correct approaches
## 8. Responsive Behavior
- Breakpoint definitions
- Responsive adaptation rules
## 9. Agent Prompt Guide
- How to use this document in AI prompts
- Example prompt templates

Now that we have covered the solution, how do you actually implement it?

Step 1: Initialize the submodule

Terminal window
# Add the upstream repository as a submodule
git submodule add https://github.com/VoltAgent/awesome-design-md.git vendor/awesome-design-md
# Initialize and update the submodule
git submodule update --init --recursive

Step 2: Create the content pipeline

Implement awesomeDesignCatalog.ts, including:

  • File scanning and validation logic
  • Markdown rendering using Astro’s built-in renderer
  • Entry data extraction

Step 3: Build the gallery UI

Use Astro + React Islands to create:

  • Homepage gallery layout (card grid)
  • Design card components
  • Search toolbar
  • Detail page layout

Step 4: Write the design document

Based on the structure of ClickHouse DESIGN.md, fill in the actual design tokens from your own project. Update README.md and add a link to design.md.

Security: Markdown rendering requires filtering unsafe HTML. Astro’s built-in renderer filters script tags by default, but you still need to watch for XSS risks.

Performance: A large number of iframe previews may affect first-paint performance. It is recommended to use loading="lazy" to lazy-load preview content.

Maintainability: design.md needs to stay in sync with the code implementation. It is recommended to add CI checks to ensure that CSS variables remain consistent between documentation and code.

Accessibility: Make sure color contrast meets the WCAG AA standard (at least 4.5:1).

After creating design.md, how do you get AI to actually use it? Here are a few practical tips:

Tip 1: Reference it explicitly in the prompt

Please refer to the design.md file in the project root and use the design conventions defined there to implement the following components:
- Buttons: use the primary color with an 8px border radius
- Cards: use the elevation-2 shadow level

Tip 2: Require AI to reference specific CSS variables

Implement a navigation bar with the following requirements:
- Use --color-bg-primary for the background color
- Use --color-border-subtle for borders
- Use --text-color-primary for text

Tip 3: Include design.md content in the system prompt

If your AI tool supports custom system prompts, you can add the core content of design.md directly to it.

Content pipeline testing:

  • Missing-file scenarios (missing README.md or DESIGN.md)
  • Format error scenarios (Markdown parsing failure)
  • Empty-directory scenarios

Search feature testing:

  • Empty result handling
  • Special characters such as Chinese and emoji
  • URL sync verification

UI component testing:

  • Light/dark theme switching
  • Responsive layout
  • Preview loading states
Terminal window
# 1. Update the submodule to the latest version
git submodule update --remote
# 2. Rebuild the site
npm run build
# 3. Deploy static assets
npm run deploy

It is recommended to automate submodule updates plus build and deployment, so that CI can be triggered automatically whenever the upstream repository is updated.

The inconsistency in AI-generated UIs that HagiCode encountered during development was, at its core, caused by the lack of a structured design reference document. By building a design gallery site and creating a standardized design.md, we successfully solved this problem.

The core value of this solution lies in:

  • Unified resources: consolidating scattered design system documentation
  • Structured standards: expressing design conventions in a format AI can understand
  • Continuous maintenance: keeping content up to date through git submodule

If you are also using AI for frontend development, this approach is worth trying. Creating a structured design.md not only improves the consistency of AI-generated code, but also helps your team maintain unified design standards internally.


If this article helped you:

Thank you for reading. If you found this article useful, feel free to like, save, and share it. This content was created with AI-assisted collaboration, and the final content was reviewed and approved by the author.