As a designer constantly exploring AI and design engineering practices, the ultimate goal is to build a true Design Harness (a design control foundation and testing sandbox) between design and engineering.

For this Harness to precisely constrain code and support high-density AI interaction reasoning, the first step must be a component library whose underlying layer is fully controlled and whose visual and engineering rules form a tightly closed loop, serving as the cornerstone. The aesthetic presets and black-box logic of ready-made open-source libraries simply cannot serve as a stable anchor for the Harness.

To lay this first foundation stone, I went through three attempts. It was only on the third attempt, when I nailed down the boundary of human-AI division of labor and restructured the foundation and feedback mechanisms, that the entire pipeline truly worked. The evolution of thinking across these three attempts, the boundaries of human-AI division of labor, and the highly specific technical pitfalls are the experiences I most want to review.

First, modify something ready-made

On my first attempt, my idea was very direct: I pulled the source code of shadcn/ui straight from GitHub and set up a React and Vite project locally. At the time I took a pure vibe approach—meaning I didn't write detailed specs and just let AI do it. After about a day of this, I chose to give up.

The main reason I wanted to give up was that I ran into several obstacles right from the start. It comes with an already-designed visual system, which means that whenever I want to follow my own rules, I have to override it somewhere, and the burden of overriding and maintaining is always there.

At the same time, the semantic linkage of global sizing and spacing that I wanted wasn't directly satisfied by its default system—for example, I wanted a batch of controls all 32px tall to share one set of padding rules, so that changing one place would change the whole batch together, and its default system didn't directly give me that kind of linkage.

To be fair, it's not that it can't be modified—it has a theme system based on CSS variables and allows extending your own tokens (that is, storing visual decisions like colors and corner radii as named variables), and the source code is open for me to modify; it's just that this time the goal was to establish my own visual rules from the ground up, and its default system had already made some aesthetic decisions, which is not the same thing as the linkage I wanted.

On top of that, the API it prepares for components seemed redundant for my own component development rules and constraints, and making changes was restricted at every turn.

改一层,还有一层:现成视觉规范带来的覆盖成本。

This round didn't go anywhere, partly because the tool's defaults didn't give me this linkage, and partly because I myself hadn't yet systematized the design rules.So for the second attempt, I decided to first organize the design rules into something AI could read directly.

Back to Figma

On the second attempt, I still used shadcn, but reversed the entire order of work. I first defined styles, variables, and basic components in Figma, then through Figma MCP—a channel that lets AI directly read that set of definitions in my Figma file—exported them and extended them at the code level.

I decide the colors, AI handles the bulk work

Using Figma to define global variables and visual styles is more direct and faster than verbally having AI guess line by line. Because the colors, corner radii, and spacing I wanted were first turned into identifiable definitions in the file, AI could just read them instead of me re-describing them over and over in conversation. The division of labor became very clear in this round: colors and gradients were decided by me—every shade of red, every shade of gray, when to use which, was entirely my design judgment;

AI was responsible for bulk processing—turning colors into variables, turning values like corner radii and blur into variables, and turning design into code. This division of labor isn't either-or, and while AI was bulk-converting variables and writing code, I could switch back to the canvas and continue reasoning about the next set of component designs without having to stop and wait for it. I define what's right; it's responsible for writing it all out and consistently.

Group components into categories by visual structure

In this round, I thoroughly deconstructed the logic of component categorization. I abandoned traditional documentation naming and grouped everything entirely by "what the visual skeleton looks like":

  • Button family: covers Button, Tag, IconButton, SplitButton, Chip. They are essentially all a background container stuffed with "icon—text—icon". Their visual layout logic is highly shared, but their HTML semantics and behavior must be kept separate.

  • Input family: covers Input, Textarea, Select, Password, OTP. The container, border, and state-activation feedback logic share the same origin, but the internal interaction logic is completely different.

  • Overlays and information feedback: Modal, Dialog, Confirm go into the dialog family; Menu, Popup, Popconfirm go into the popover family; Alert, Toast, Message go into the notification family.

The design assets distilled from this categorization are very solid and are still running stably in my project today. But I was still stuck: the React + Vite preview environment I built by hand had become a new burden. I found that while working on design rules, I also had to spend a lot of energy patching up that temporary page for viewing components, and my focus was severely diluted.

What I categorized was their visual structure; as for how they appear and when they close, that must be defined separately per component. The most concrete analogy is buttons and tags: they are visually very close, both being a background container arranged as "icon—text—icon", and this visual part can be shared; but behavior and HTML semantics still need to be defined separately—visual similarity doesn't mean they can be merged into one thing;

Nor is the dropdown in the same family just a matter of renaming the input's label. This categorization is a personal method I've accumulated over long-term component library work, not some industry standard; visual reuse doesn't mean behavior and semantics can be reused.

按钮和标签可以共享视觉结构,行为与语义仍要分别定义。

The design foundation distilled from the second attempt is valid, and my project is still using it today; but I was still dissatisfied overall. The redundancy of encapsulation remained, and I also had to keep maintaining the React + Vite preview environment I built myself—I was effectively maintaining both a design and a tool for viewing the design at the same time.

Change the foundation, write judgments as rules

Before making the third attempt, I chatted with a friend, who suggested I look at Storybook. I had also thought of Base UI, a headless React component library. I didn't start from zero; instead, I built on the design results of the second attempt and changed the foundation, completing the component library development planned at the time in just one day and achieving my personal visual satisfaction. This time it was a success.

Let the foundation manage behavior, keep styling for yourself

First, Base UI took care of behavior and structure. "Headless" can be understood as the foundation providing component behavior and composable structure, with styles defined by me. But headless doesn't mean there's no API, doesn't mean all components are complete, and doesn't mean it automatically meets my design needs.

Give colors names that explain their purpose

At the styling level, semantic variables took care of visual consistency. Semantic variables are named by purpose rather than by color—specific purposes like text, border, background, interaction, and surface are given explicit names. In the naming rules, negative refers to the category of negative or dangerous actions, intense refers to the darker background tier used for emphasis, and hover refers to the mouse hover state.

This is a small excerpt selected from my project's token file, not the complete stylesheet.

:root {
  --nico-color-text: var(--nico-color-grey-alpha-1300);
  --nico-color-text-negative: var(--nico-color-red-500);
  --nico-color-border: var(--nico-color-grey-alpha-400);
  --nico-color-border-negative: var(--nico-color-red-500);
  --nico-color-background-input: var(--nico-color-grey-alpha-300);
  --nico-color-background-negative-intense: var(--nico-color-red-500);
  --nico-color-background-negative-intense-hover: var(--nico-color-red-600);
  --nico-color-interaction-hover: var(--nico-color-grey-alpha-300);
  --nico-color-interaction-hover-negative: rgb(from var(--nico-color-background-negative-intense) r g b / 0.04);
  --nico-color-surface: var(--nico-color-grey-opaque-100);
}

The organizational logic of this code can be broadly divided into three layers: the bottom layer is tiers of color scales, such as red-500 and grey-alpha-400; the middle layer is semantic names based on purpose, covering text, border, background, interaction, surface; specific components reference these semantic names rather than referencing the color scales directly.

In one sentence: the text color, border color, background color, and hover color for a dangerous action can all be pointed to by name.

For example, a button's negative plain and ghost use interaction-hover-negative, and negative filled uses background-negative-intense and its hover.

Precisely because there are these names that can be pointed to, when adjustments are needed later, for example, I can tell AI "make the negative intense hover one tier darker" without having to re-describe the color. With an explicit variable layer, when I'm dissatisfied with something in Storybook, I can give specific modification requirements;

But this only establishes clear reference rules; it doesn't mean all components automatically follow the semantic variables.

Look in Storybook, then have AI modify

At the interface debugging stage, Storybook took care of the feedback loop for me. It let me lay out and view a single state of a single component, with docs and tests. My loop became: look with my eyes, feel dissatisfied, give AI a specific modification requirement, look again.

Storybook is not a replacement for React or Vite; what I replaced was the display workbench I built myself, and the project is still a standard React + Vite integration.

For these to work, the premise is that I already had the styles, variables, and basic components settled in the second attempt, and this part was directly inherited when changing the foundation, with no rework. My project now depends on Base UI, and the development and build entry uses Storybook's react-vite integration.

看见状态差异,给出具体修改,再看一次。

Throughout the whole exploration process, I also stepped into quite a few specific pitfalls.

Some things to note

One type of pitfall is that the wording isn't specific enough. For example, duration—when I asked AI to do duration, it never understood; what I actually wanted was a start-end interval, and only saying start and end matched it. At the time I didn't get this done.

What needs to be distinguished is that the project now already has a start/end implementation under this name, so it's not that it can never be done, but that the wording at the time failed to make it match my needs. Another example is readonly and disabled: they represent the two states of read-only and disabled, and they are not equivalent; the property names alone aren't enough—you have to spell out whether it can be focused, whether it can open a panel, whether it can change the value.

This is essentially a failure to align on the behavioral contract between us, not that the library doesn't understand human language, and the two properties can't be said to be equivalent. For example, if I want these two states to differ as follows, I can tell AI explicitly: "In the read-only state it can be focused but can't invoke the selection panel; in the disabled state it can't be focused and doesn't respond to interaction", making the contract fully clear.

Another type of pitfall is requirements that need secondary development on my own. The time precision and composite controls I expected still had to be built by me; the time tool I wrote myself can now support down to minutes, seconds, and milliseconds,this is a component built in the project, not something Base UI natively provides; this type of pitfall can't be solved just by speaking more clearly—it really requires writing code.

One type is to clarify the contract first and then check the implementation; the other type explicitly requires secondary development. Clarifying the rules, holding the design assets in hand, choosing a foundation that returns styling to me, plus a feedback loop that lets me clearly see individual states—these four things together let me achieve visual satisfaction within the scope planned at the time, and anything beyond that scope still requires secondary development. What was truly saved was my energy spent maintaining "the tool for viewing designs" and the cost of explaining the visuals over and over.

Summary

The first step in building a Design Harness is essentially solving the problems of "control"and"explanation cost": if the underlying components carry someone else's aesthetic black box, the subsequent Harness cannot achieve fully controlled monitoring and intervention of UI states; if your own design language isn't abstracted into a precise Token contract, AI's execution in the engineering pipeline will fall into endless vague tug-of-war.

For designer friends new to design engineering practice, the key to landing visuals in code is how to turn your own visual judgments into named, identifiable rules, and truly see every subtle feedback of a component in a clean environment. Don't rush to override others' preset aesthetics in code; first establish your own semantic constraints on the design side, then hand structure over to a suitable headless foundation to carry it.

Once language and rules are aligned, code and tools can truly run along with your intuition.

Abstract design rules into named, identifiable Tokens; choose a headless foundation stripped of aesthetic baggage (Base UI); use a professional sliced sandbox (Storybook) to establish a feedback loop. With these three done, this component cornerstone is truly established, and building the complete Design Harness afterward will have a solid grip.