Zero to Design Engineer
For UX and product designers who want to write real TypeScript, ship React and Vue components, and contribute to a shared engineering codebase without breaking things. You'll build almost everything with Claude Code.
You already know design. You understand users, hierarchy, interaction patterns, and what good looks like on screen. What you're adding is the ability to build it in code and land it in a shared codebase without breaking anything.
You don't need to memorize syntax. You need to understand concepts well enough to direct Claude Code, read what it produces, catch when it's wrong, and fit it into the team's codebase. That's the job.
Orientation & Environment Setup
Before any code, you need a working environment. Think of it like setting up your Figma workspace and shared libraries for the first time. It's tedious and you only do it once, but skipping it makes everything downstream harder.
VS Code
Your code editor. Free, standard everywhere. Install it first.
Claude Code
Your primary build tool. Install it and connect it to a project folder.
Node.js
The runtime that lets JavaScript/TypeScript run locally. Install the current LTS version.
Git
Version control — how teams share code. Install now, learn deeply in Module 6.
GitHub Account
Where shared repositories live. Free tier is fine.
Terminal
VS Code has one built in — use it. It's how you run commands.
The mental model shift: In design tools, your file is the artifact. In code, source files are instructions and a build process turns them into the running app. Edit text, compile, view in browser, repeat. That loop is everything.
Ask Claude Code to verify your setup: "Check that Node, npm, and git are installed and tell me the versions. If any are missing, tell me exactly how to install them on my OS." Get comfortable using it to diagnose your environment. You'll do this a lot.
- Install VS Code, Node.js, Git, and Claude Code
- In a terminal, run node --version, npm --version, and git --version — confirm each prints a version number
- Create an empty folder, open it in VS Code, and open the built-in terminal inside it
You can open a project folder in VS Code, open a terminal inside it, and all three version commands return numbers without errors.
The Web Platform: HTML, CSS & the Browser
You already think in components, layout, spacing, and states. HTML and CSS are the native language of those ideas. Once you understand how the browser lays things out, CSS starts making sense instead of feeling arbitrary.
HTML is structure and meaning. Every element has a semantic purpose. Use the right element — a <button> not a <div> that acts like one — for accessibility and readability.
<article>
<h2>Card title</h2>
<p>Supporting copy.</p>
<button>Take action</button>
</article>
Modern layout = Flexbox and Grid. Flexbox is one-directional (think Figma auto-layout). Grid is two-dimensional (think layout grids with defined columns).
.row {
display: flex;
gap: 16px;
align-items: center;
justify-content: space-between;
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
DevTools are your Figma inspector. Right-click → Inspect on any page. See the HTML tree, the CSS on any element, and edit live. This is where you'll diagnose layout issues forever.
Claude Code writes HTML/CSS fluently. Your job is to evaluate it. Is the markup semantic? Is spacing using consistent tokens or magic numbers? Ask it to explain why it used grid instead of flexbox, and make sure the answer actually makes sense. That's how judgment builds.
- Build a static "profile card" in plain HTML and CSS — avatar, name, role, a button. No frameworks.
- Use Flexbox to lay out three cards in a responsive row that wraps on small screens
- Open DevTools on a site you admire, find a button, and identify its padding, font-size, and border-radius
You can look at a rendered component, roughly predict the HTML structure and key CSS, and use DevTools to confirm or correct your guess.
JavaScript Fundamentals
JavaScript is where things do something: clicks, data, logic, state changes. You need to be able to read it, trace what it does, and catch when Claude Code's logic is wrong. This is the module where the real work starts.
Variables & types: const for values that don't change, let for ones that do.
const name = "Ada"; // string
const count = 3; // number
const isActive = true; // boolean
const tags = ["ux", "code"]; // array
const user = { id: 1, name: "Ada" }; // object
Array methods — learn these three cold. They're 80% of real UI code:
// .map() — transform every item (data → components)
const names = users.map((u) => u.name);
// .filter() — keep items that match a condition
const admins = users.filter((u) => u.role === "admin");
// .find() — get the first match
const ada = users.find((u) => u.id === 1);
Async basics — fetching data takes time. async/await waits without freezing the UI:
const loadUser = async (id) => {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
return data;
};
Use Claude Code as a tutor here, not just a generator. Paste a snippet and ask it to walk you through what it does line by line, as if you're a designer learning to code. Read every line. If you can't explain it, ask before you accept it.
- Given an array of product objects, use .filter() and .map() to produce an array of names of products under $50
- Write a function that takes a user object and returns a greeting string
- Read three short JavaScript snippets Claude Code generates and explain each out loud before running them
You can read a 20-line piece of UI logic — loops, conditionals, function calls — and explain what it does and what it would render, without running it first.
TypeScript
TypeScript adds types to JavaScript: labels that describe what shape your data is. A Button that only accepts variant: "primary" | "secondary" won't let you pass "purple" by accident. Types catch that class of mistake before the app runs. Every serious team codebase uses it.
Interfaces describe the shape of an object — like documenting the props of a Figma component:
interface User {
id: number;
name: string;
role: "admin" | "member"; // union — only these values allowed
avatarUrl?: string; // ? = optional
}
const greet = (user: User): string => `Hi, ${user.name}`;
Reading type errors is the real skill. They almost always say "you said this was an X, but here it's a Y." Read the last line of the error first. Ask Claude Code: "explain this error in plain terms and show me the smallest fix."
When Claude Code generates an interface, read it like a spec. "So a Product always has a price as a number and an optional discount." When you hit a red squiggly, paste the error and ask Claude Code to explain it. Over time you'll start predicting the fixes before you ask.
- Define an interface for a Product — id, name, price, optional tags array
- Write a typed function that takes a Product[] and returns the total price
- Intentionally pass the wrong type into a function, read the TypeScript error, and fix it
You can read an interface and describe in plain English what data is required vs. optional, and interpret a basic type error and fix it without panic.
React: Thinking in Components
You already think in components. You build them in Figma every day. React is the same idea made interactive. A React component is a function that returns UI, and your design background gives you a real head start here.
Props are inputs — exactly like component properties/variants in Figma:
interface CardProps {
name: string;
role: string;
}
function ProfileCard({ name, role }: CardProps) {
return (
<article className="card">
<h2>{name}</h2>
<p>{role}</p>
</article>
);
}
// Used like:
<ProfileCard name="Ada" role="Design Engineer" />
State is data that changes over time and re-renders the UI — think "interactive variants that respond to the user":
import { useState } from "react";
function FavoriteButton() {
const [favorited, setFavorited] = useState(false);
return (
<button onClick={() => setFavorited(!favorited)}>
{favorited ? "★ Saved" : "☆ Save"}
</button>
);
}
Rendering lists — .map() turns data into components. Always include a key prop:
{products.map((product) => (
<ProductCard
key={product.id}
name={product.name}
price={product.price}
/>
))}
Describe what you want the way you'd brief an engineer: "Build a ProductCard that takes name, price, and an optional badge. Use our spacing tokens. Include hover and loading states." Then review the output like a design critique. Are the props sensible? Is state in the right place?
- Build a Button with a variant prop ("primary" | "secondary") that changes its styling
- Build a ProductCard that takes typed props and renders them
- Build a list view that maps an array of data into ProductCard components
- Add a favorite toggle — a piece of state per card that switches between saved and unsaved
You can describe any UI as a tree of components with props and state, decide what belongs where, and read a React component someone else wrote and explain what renders and when.
Organizing Code Like a Team Does
Organizing a codebase is design-systems thinking applied to files. Messy, inconsistent code gets rejected in review the same way an inconsistent design does. Clean file structure is one of the first things engineers look for in a PR from someone new.
src/
components/ # reusable UI (Button, Card, Modal)
features/ # components grouped by product area
hooks/ # reusable logic (useAuth, useProducts)
lib/ or utils/ # helper functions
types/ # shared TypeScript interfaces
styles/ # tokens, global styles
Design tokens in code — your design system's values become CSS variables. Often literally your job as a design engineer:
:root {
--color-primary: #C4633F;
--space-2: 8px;
--space-4: 16px;
--space-8: 32px;
--radius-md: 4px;
}
The golden rule: Follow the team's existing pattern, not your personal preference. One component per file, PascalCase names: ProductCard.tsx exports ProductCard. Consistency is worth more than your favorite approach.
Ask it to split a bloated component: "This component is doing too much. Move the data-fetching into a hook and keep the component presentational." But you own the structure decisions. You're the one defending them in code review.
- Take your Module 4 components and organize them into the proper folder structure
- Extract colors and spacing into a CSS variables tokens file — no more hard-coded values
- Pull repeated logic out into a utils function or custom hook
You can look at a feature and decide where each file should live, name things consistently, and explain why code belongs in a component vs. a hook vs. a utility.
Git & Version Control
Git is version history, branching, and multiplayer for code. Without it you cannot contribute to a shared repo. You'll use about eight commands 95% of the time, and Claude Code can run and explain every one of them.
# 1. Get the latest team code before starting
git pull
# 2. Create a branch for your work
git checkout -b feature/product-card-hover
# 3. Do your work and save files...
# 4. Stage your changes
git add .
# 5. Commit with a clear, present-tense message
git commit -m "Add hover state to ProductCard"
# 6. Send your branch to GitHub
git push
# 7. Open a Pull Request on GitHub ↗
main / trunk
The shared, must-always-work version. Never commit directly to it on a team.
branch
An isolated copy to work on. Changes stay isolated until merged.
commit message
Like a good layer name — short, clear, present-tense. "Fix spacing on mobile nav" not "stuff."
merge conflict
Two people changed the same lines. Git asks which to keep. Alarming but routine.
Claude Code can run git commands and narrate what each one does. Ask it: "Show me exactly what changed, write a clear commit message, and explain each step." When something goes wrong, paste the error and ask for the safe fix. Repeat the loop until it's automatic.
- Initialize a Git repo for one of your projects and make your first commit
- Create a branch, make a change, commit it, and merge it back to main
- Deliberately create a tiny merge conflict and resolve it with Claude Code's help
- Push a branch to GitHub
You can run the full branch → commit → push loop without looking it up, write a clear commit message, and stay calm when you hit a conflict because you know it's just a question to answer.
Working in a Shared Team Repository
The earlier modules were practice. This one is the actual job: getting your code into a codebase others depend on, through the team's process, without breaking anything. Doing this well is how engineers start trusting your work.
Pull Requests (PRs) — how you propose changes. A good PR is:
Small & focused
One logical change — like a tight design crit, not a 200-screen dump
Clear description
What changed and why. Engineers should never wonder what you did.
Before/after screenshots
For any UI change. Your design instincts make you great at this — own it.
CI passing ✓
Automated checks (build, lint, tests) must be green before requesting review.
Code review is design critique with a different medium. Respond graciously, make changes, re-request review. Don't take it personally. It's how quality stays high. Follow the team's pattern, not your personal preference.
Use it to onboard fast. Ask: "Give me a map of this codebase. Where do components live, how is styling done, what conventions do they follow?" Before writing anything, ask it to find an existing component similar to yours so you can match the pattern.
- Clone a real open-source repo. Have Claude Code map it, then verify by exploring yourself.
- Find an existing component and describe the team's patterns: file structure, styling, naming
- Make a tiny safe change, open a PR with a proper description and screenshot, and walk it through CI
You can land in an unfamiliar repo, identify how the team does things, make a focused change matching their patterns, open a clean PR, get it through CI and review, and merge it.
Vue Translation
Vue and React solve the same problems with slightly different syntax. If your team uses Vue, everything from Modules 1–7 still applies. Only the component syntax changes.
<script setup lang="ts">
import { ref } from "vue";
interface Props { name: string; role: string; }
defineProps<Props>();
const favorited = ref(false);
</script>
<template>
<article class="card">
<h2>{{ name }}</h2>
<p>{{ role }}</p>
<button @click="favorited = !favorited">
{{ favorited ? '★' : '☆' }}
</button>
</article>
</template>
<style scoped>
.card { padding: var(--space-4); }
</style>
The concept map — React → Vue:
| React | Vue 3 | What it does |
|---|---|---|
| .tsx component | .vue SFC | The component file format |
| Props interface | defineProps<Props>() | Typed component inputs |
| useState(false) | ref(false) | Reactive state |
| useEffect | watch / onMounted | Side effects and lifecycle |
| .map() in JSX | v-for in template | Rendering lists |
| && / ternary | v-if / v-show | Conditional rendering |
| onClick={fn} | @click="fn" | Event handling |
| className | class | CSS class attribute |
TypeScript, code organization, git, PRs, and testing are identical between React and Vue. The framework is the smallest part of the job.
Tell Claude Code your team uses Vue 3 with the Composition API and <script setup> and it will match that style. Ask it to "show this component in both React and Vue side by side" to cement the mapping.
You can read a Vue SFC and map each part to the equivalent React concept, and build a basic Vue component with typed props and reactive state.
Testing & Smoke Testing
Smoke testing means checking that the basics work before you ship. For a design engineer, that means verifying your component handles the obvious cases well enough that you're not the one who broke something in the next release.
1. Build passes
App builds with no errors in the terminal
2. Happy path works
The main use case renders and functions correctly
3. Edge states
Empty, loading, and error states all render
4. Responsive
Works at mobile and desktop widths
5. No console errors
Zero new red errors in the browser console
6. Keyboard nav
Tab through it. Focus states visible.
7. Edge case text
Very long text, empty strings — layout holds?
8. Lint + types
Run lint and typecheck locally before pushing
Automated tests verify behavior without clicking. Vitest + Testing Library is the common tool:
import { render, screen } from "@testing-library/react";
import { ProductCard } from "./ProductCard";
test("shows the product name", () => {
render(<ProductCard name="Notebook" price={12} />);
expect(screen.getByText("Notebook")).toBeInTheDocument();
});
test("shows empty state when no products", () => {
render(<ProductList products={[]} />);
expect(screen.getByText(/no products/i)).toBeInTheDocument();
});
Ask it to write smoke tests covering the happy path, empty state, and error state. Then read them to confirm they actually test the right things. A test that always passes tells you nothing.
- Run your ProductCard through the smoke-test checklist manually
- Have Claude Code write three component tests; read each and confirm what it's verifying
- Deliberately break a component and watch a test fail. Fix it and watch it pass.
- Run lint, typecheck, and tests locally and get them all green
Before opening any PR, you instinctively run through the smoke-test checklist. You can read a test and say what it verifies. You catch your own broken states before a teammate does.
Building Effectively with Claude Code
This is your primary build tool, so using it well matters. A designer who generates code and ships it without understanding it is not a design engineer. The job is judgment: knowing what to ask for, how to review what comes back, and when to verify before accepting.
Brief like an engineer
Include: the stack, conventions, the file it lives in, props, states, edge cases. Vague in, vague out.
Work in small steps
One component at a time. Review, then continue — same as iterating on a design.
Review like a critique
Understand every part. Does it match team patterns? What edge cases did it miss?
Use it to learn
Ask "explain this," "why this approach," "what would an engineer flag?" Every feature is a lesson.
Verify, don't assume
Run it. Smoke-test it. Check the console. AI-generated code can be confidently wrong on edge cases.
Own the architecture
Let Claude Code write the code. You decide the structure, component boundaries, and where state lives.
The full end-to-end workflow:
- Understand the task and existing code — map relevant files before writing anything
- Brief clearly, referencing existing patterns and conventions in the repo
- Generate in small steps, reviewing and understanding each piece before continuing
- Integrate into the right files with the right structure and naming
- Smoke test against your Module 9 checklist — every state, every edge case
- Run lint, typecheck, and tests locally — get to green before pushing
- Commit with clear messages, push, open a focused PR with description and screenshots
- Respond to review, understand each change before pushing it, then merge
You can take a feature from "design is ready" to merged in the team repo, directing Claude Code throughout, and you understand and can defend every line that ships.
Build & Ship: Saved Items Feature
Feature: Saved Items
Build a product list app with a "Saved Items" feature from scratch. A list view that fetches and renders typed ProductCard components, a save/favorite toggle per card, a "Saved" filtered view, empty/loading/error states handled, responsive and keyboard-accessible, organized into a proper file structure using design tokens.
The process:
- Scaffold a fresh React + TypeScript + Vite project (have Claude Code do it)
- Build in small, reviewed steps — own all structure and architecture decisions yourself
- Type everything: props, state, data fetching, utility functions
- Write a smoke-test checklist and run through it. Write component tests for key behaviors.
- Run lint, typecheck, and tests until all are green
- Initialize a Git repo, commit in logical chunks with clear messages
- Open a PR against a main branch with a proper description and screenshots
- Write a short reflection: what did you understand vs. just accept?
You're a design engineer when you can do all of that, explain every decision, and honestly say you understand and stand behind the code. That's where this course is taking you.