← All work / Design System & Development

2026 / 12 min read

Pomagam.pl

A design system reverse-engineered from a live site, designed in Figma, then shipped as ~70 verified React components by directing AI agents.

Pomagam.pl is one of the largest online fundraising platforms in Poland and has been running since 2014. People use it to raise money for medical treatment, animals, local initiatives, and anything else legal, and the platform takes 0% commission on donations and payouts. The product grew feature by feature, and every new feature went straight onto the live site.

There was no Figma file, no design library, and no project archive. The site itself was the only record of how anything was supposed to look. I was brought in to turn that into a design system the team could build on, and then to turn the design system into a production React library.

Disclaimer: All views expressed are my own and do not necessarily reflect those of the company. Certain design elements have been modified from the original.

My Role

I worked on Pomagam.pl as the UI/UX and design system designer, and then as a design engineer. The team was small: a CTO, a product owner, one full-stack developer, and me.

The work had four parts. I audited the live product to find the patterns it already used. I designed the system in Figma, including foundations, components, documentation, and responsive and accessibility rules. I built the React component library and its Storybook by directing Claude Code agents through a pipeline I designed and ran. And I designed the QA process that checks what the agents produce. I didn't write most of the code. I designed the system, set the rules, ran the pipeline, and caught it when it was wrong.


Starting Without a Source of Truth

With no design files, every decision had to start from the live site. Before any component existed in Figma, I had to work out what the product already was: which patterns repeated, which ones were one-offs added for a single feature, and what a design system would need to cover so that new features stopped reinventing old ones.

Two screens do the product's real work, and the system had to get both right. The campaign page is where a donor decides. Its right-hand column stacks the goal and a progress bar, a line of social proof ("42 people donated in 2 days"), one filled primary button ("Donate now"), a quieter "Share" button with a share count, and then the organizer and the recent donations. On mobile the donate button sits directly under the progress bar and the organizer shrinks to a single row. The campaign creation form is where an organizer commits: one card per step, a character counter on the title, a large validated amount field, and a red "Tip" label above the photo and story fields with plain-language advice, including a checklist of what to write about.

A missing source of truth hurts a small team more than a big one. With one full-stack developer shipping features, every screen designed from scratch in code takes time away from the product, and every small visual difference becomes permanent the moment it goes live.


Making the Case for a Design System

Before building anything, I presented the plan to the team, along with what a design system would give Pomagam.pl in practice:

Responsive by default. Components carry their own breakpoint rules, so a new page works on mobile without a separate design pass.

Faster from idea to live page. New features get assembled from existing, tested parts instead of being designed and coded from zero.

A/B testing without a redesign. Variants built from the same components can go live side by side, so the team can test an idea without committing to it.

One source of truth. Design and code point to the same components, so the question "which button is the right one?" has one answer.

Accessibility checked once, inherited everywhere. Contrast, focus states, and keyboard behavior get fixed at the component level instead of page by page.

Less rework for a small team. Fewer one-off screens means the full-stack developer spends time on the product, not on rebuilding the same card for the fifth time.

The CTO and PO agreed, and I started in Figma.


Designing the System in Figma

I rebuilt the product in Figma as a system, starting from the foundations: typography, spacing, icons, and four layout grids (4 columns on smartphone, 8 on tablet, 12 on desktop and large desktop). Components sit on top, with their variants, documentation for how and when to use them, and responsive rules for each breakpoint. The goal was a library the team could trust and build new features on.

Every component page follows one template, so nobody has to learn a new layout each time: a responsive badge, a one-line purpose, a usage note, a build-status banner that says which parts are already coded in Storybook, then sizes, states, hierarchy, and mobile behavior. The Button page shows the level of detail. It defines six variants (filled, success, outlined, outlined brand, text, link), four sizes from 40 to 64px high, and exactly four states: default, hover, loading, and disabled. The hierarchy is fixed (filled, then outlined, then text), the outlined brand variant is the system's destructive button, and buttons always sit 12px apart. A click isn't a separate state, because the ripple that spreads from the click point already signals it. On small screens the buttons change orientation and size, and MD becomes the smallest one available.

To check that the system held up, I tested it on page templates built only from the new components. Templates show quickly where a component is missing, where a variant doesn't cover a real use case, and where a responsive rule breaks on an actual page layout. After each round I reviewed the results with the team and confirmed the direction before moving on. By the end, the team had a Figma file to use as the source of truth for new work.


From Figma to Storybook, by Directing AI

The Figma system then had to become a production React library that Claude itself would later use to assemble campaign pages. Anything subtly wrong in a component gets multiplied across every page built on top of it, and nobody catches that by eye later.

AI can write a React component. The harder problem was building a process where it could produce ~70 of them and I could trust the result.

Five specialized agents instead of one generalist. I set up purpose-built Claude Code subagents: one that enumerates the Figma file into a machine-readable inventory, one that extracts design tokens (colors, spacing, typography) from Figma's own variables instead of hand-typed values, one that builds components in small batches, one that runs visual and accessibility QA against the live Figma design, and one senior-review agent that reads the diff critically and files findings.

Narrow tool access on purpose. Each of my agents gets an explicit tool list. The QA agent can take screenshots and measure, but it can't edit code, so it can't "fix" a problem by quietly redefining what passing means.

A model per job. Cheap models handled mechanical enumeration, stronger ones did the building, and the strongest were reserved for review. That routing was a cost decision as much as a quality one. Running everything on the biggest model would have been wasteful, and running everything on the cheapest would have shipped garbage.

A loop that only escalates when it has to. I chained the agents into a build, QA, review, fix, and re-verify loop that runs unattended and only comes back to me when it hits something a human has to decide. At peak, I had a dozen-plus of these pipelines running in parallel against different parts of the library.

Guardrails That Make AI Output Checkable

A single source of truth, in code too. Every component maps back to a specific Figma node in a validated JSON map. An agent that receives a Figma URL checks that map first, so no component gets built twice under two names, and no value drifts from the design without it being visible.

No "inferred" and no "for consistency". This became a hard project rule after I noticed that every accuracy bug traced back to the same cause: something wasn't obvious, so the AI wrote a plausible answer instead of checking. A line like "no exact spec, using X for consistency" now counts as a red flag to investigate.

Automated drift detection. A linter fails the build if a component falls back to a raw hex color or an arbitrary pixel value instead of the real design token. That kind of rot passes review and slowly degrades a design system over months.

Stop and ask, enforced. When Figma has no equivalent for something, the builder agent has to stop and ask a human instead of inventing a component nobody approved.


Catching It When It's Wrong

AI agents fail in specific, recognizable ways. A good part of my work on this project was learning those patterns and designing around them.

It reported success on work it hadn't done. A QA agent told me "5/5 components PASS" with confident specifics. Four of the five report files it had written said "not yet run." I caught it by checking the files instead of trusting the summary. Checking the files an agent claims it wrote became a standing step, and I added it to the project's persistent memory so nobody would have to learn it again.

It read code instead of testing it. Agents kept "verifying" components by re-reading the source instead of rendering them in a browser. I rewrote the QA process to require real evidence: computed styles, measured dimensions, and actual hover and keyboard interaction. Passes without that evidence got rejected. More than once I sent the same check back three or four times before it came back honest.

It measured the wrong thing and reported a pass. One QA pass "confirmed" responsive behavior by resizing a wrapper element instead of the browser viewport, which can't trigger CSS breakpoints at all. The check could never have found the bug it claimed to clear. I traced that one to a missing tool permission in my own agent config and fixed it there.

It hid a real limitation behind confident language. An agent documented a modal as having working focus trapping, when the underlying library shipped no focus management at all. I corrected it and documented the gap honestly.

That discipline caught real bugs before they shipped:

  • a currency input whose value prop wasn't truly controlled, so the parent couldn't reject a keystroke
  • a text field that threw away what the user had typed when the browser crossed a responsive breakpoint, because the mobile and desktop versions had become two separate uncontrolled inputs
  • a modal with no keyboard focus trap
  • brand-colored text failing WCAG contrast at 2.97:1
  • a loading button that removed its label from the page while it spun, leaving screen readers a disabled control with no name
  • Polish plural grammar ("1 dzień", "2 dni", "5 dni") that was correct only for the three example numbers the design happened to use

None of these are exotic. They're what slips through when "it looks fine" replaces verification.


Decisions I Kept for Myself

When to deviate from the design file. Figma defined a pressed state for buttons. We dropped it in favor of the component library's own ripple feedback and recorded it as a dated product decision, so no future pass "fixes" it back. The ripple stays off for the inline link variant, where it doesn't suit underlined text.

When accessibility beats the literal spec. A loading button swaps its label for a spinner. The first build removed the label from the page, so a pending button became a disabled control with no accessible name. The label now stays in the page, visually hidden, and the button sets aria-busy. The reason is written in the code next to the fix.

When the library underneath isn't enough. The Accordion is built on Material Tailwind, which doesn't set aria-expanded or aria-controls. Our wrapper adds both, and it removes the closed body from the accessibility tree and the tab order instead of only clipping it visually.

When design and code should share a vocabulary. Component props mirror Figma properties. The Accordion's variant is Figma's Type property, and defaultOpen maps to State=closed. Designers and developers talk about the same thing, and a reviewer can check a prop against the file.

When a mobile variant is one component and when it's two. Some Figma mobile variants are the same component at a smaller size. Others are different enough in structure to justify a separate export. The organizer block on the campaign page is an example: a full card with an avatar and an "Ask a question" button on desktop, a single row on mobile, and the library exports both as CardOrganizer and CardOrganizerCompact. That choice has real API consequences, so I made it case by case instead of letting a rule decide.

When the design system itself is wrong. Half the type scale changes size across breakpoints in Figma, but the utility-class system had no way to express that. Instead of patching ~35 components one by one, I had the fix land at the token layer and made the CSS variables themselves breakpoint-aware. Every existing class became responsive with zero component changes and no risk of missing one.

When to stop. A tooltip component got built, tested, and reviewed, and then the team decided it wouldn't be used. It's unexported and hidden instead of deleted, with the reason recorded, so the work isn't lost and nobody rebuilds it by accident.

What Marketing Does With It

The marketing team now generates new pages from the components and templates I designed, and the result comes out very close to the real site. Pages that used to wait for the full-stack developer can start from parts that are already designed, documented, and tested.


The Outcome

  • ~70 production components, each traced to a specific Figma node and verified against it
  • Foundations and documentation, with a layout grid for each screen size and a Polish documentation page for every component, and props named after Figma properties
  • Design tokens generated from Figma's own variables, including a responsive type system where one utility class resolves to four sizes across breakpoints
  • ~700 automated tests, plus visual-diff, accessibility, and token-drift checks wired into the workflow
  • A live component catalog in Storybook, deployed through CI behind an access gate
  • A reusable pipeline: the same agent setup and verification rules now carry over to building pages

Stack. React 18.3, TypeScript 5 (strict), Tailwind CSS 3.4, Material Tailwind, Storybook 8, npm workspaces, Vitest and Testing Library, Playwright, Figma API for tokens and component specs, GitHub Actions and GitHub Pages, and Claude Code with custom subagents, scoped tool permissions, per-stage model routing, and persistent project memory.

What transfers. The component library is specific to Pomagam.pl. Three habits carry over to other projects: reading a live product closely enough to find the system already inside it, designing components so that accessibility and responsive behavior are part of the spec from the start, and directing AI agents with scoped tools, evidence-based checks, and a clear rule for when to stop the machine and make the call myself.