Build an HCI Design Assistant with Claude

Generate HCI-grounded UI designs from natural language using Claude API.

Introduction

30 Second Summary

Every button you tap, every form you fill out, and every error message you read was shaped by decades of research into how humans interact with computers. Most developers skip this research entirely, leaving users frustrated by interfaces that ignore basic usability principles.

In this project, you will build a web application that uses the Claude API to generate frontend designs grounded in Human-Computer Interaction (HCI) principles, complete with an audit panel explaining the research behind every design decision.

What You'll Build

A local Node.js application with a split-panel interface where you describe a UI component in plain English and receive both a live HTML/CSS preview and a structured HCI audit.

By the end of this project, you'll have:

  • An Express server that proxies requests to the Claude API with an HCI-focused system prompt encoding 10+ usability principles as actionable design rules.
  • A vanilla HTML/CSS/JS frontend with a live preview rendered in an iframe and an HCI audit panel that makes AI reasoning transparent.
  • A carefully crafted system prompt encoding Nielsen's heuristics, Fitts's Law, Norman's design principles, and WCAG accessibility into concrete, measurable constraints.
  • Secret Mission: A "Redesign with Focus" mode that lets users select a specific HCI principle to emphasize in the generated design.

Are there any prerequisites?

You should be comfortable with HTML, CSS, and JavaScript basics. You also need Node.js installed on your machine. No prior experience with AI APIs or HCI theory is required.

Before We Start

Before We Start...

Set Up Your Project and API Key

Before you can build a design assistant, you need a way to talk to Claude programmatically. The Anthropic API lets your code send prompts to Claude and receive structured responses back.

In this step, you will create an Anthropic account, grab an API key, and set up a Node.js project with the dependencies your app needs. You will finish by sending a test message to confirm everything is wired up correctly.

In this step, get ready to:
  • Create an Anthropic Console account and generate an API key.
  • Initialize a Node.js project and install the required packages.
  • Verify the setup works by calling the Claude API from a test script.
Create an Anthropic Console account and generate an API key

The Claude API requires an API key for authentication. You will create a free account on the Anthropic Console and generate a key that your project uses to send requests.

  • Open your browser and go to console.anthropic.com.
  • Click Sign Up and create an account using your email address.
  • Complete the SMS phone verification when prompted.
  • Once signed in, navigate to the API Keys section in the sidebar.
  • Click Create Key, give it a name (e.g. hci-design-assistant), and click Create.
  • Copy the key immediately. You will not be able to see it again after closing the dialog.

What about the free credits?

New Anthropic accounts receive approximately $5 in free API credits that expire after 14 days. This project takes under an hour, so you are comfortably within both the credit budget and the time window. No payment method is required.

The model you will use (Claude Haiku 4.5) costs $1 per million input tokens and $5 per million output tokens. A typical design generation uses around 2000 tokens total, costing less than $0.01 per request.

Initialize the Node.js project and install dependencies

Your project needs two packages: the official Anthropic SDK for calling the Claude API, and Express for serving your frontend files later.

Can't install Node.js locally?

If your machine restricts software installs, browser-based Node.js environments like StackBlitz can run Node.js projects in a WebContainer. However, this project's instructions assume local tooling (terminal, Cursor, Desktop file paths) and have not been adapted for a browser IDE. You will need to translate steps like "open your terminal" and "create a file in Cursor" into that environment's equivalents on your own.

  • Open your terminal by pressing Cmd+Space (macOS) or the Windows key (Windows), then type Terminal and press Enter.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir hci-design-assistant
cd hci-design-assistant
  • Initialize a new Node.js project by running:
npm init -y
  • Set the project to use ES modules by running:
npm pkg set type=module
  • Install the Anthropic SDK and Express by running:
npm install @anthropic-ai/sdk express

What do these packages do?

`@anthropic-ai/sdk` is the official client library for calling the Claude API from JavaScript. It handles authentication, request formatting, and response parsing for you.

`express` is a web server framework that you will use to serve your frontend files and create an API endpoint for your design assistant.

  • Confirm the packages installed correctly by running:
ls node_modules

You should see a list of folders including @anthropic-ai and express in the output.

Seeing permission errors during install?

If npm install fails with permission errors, try running your terminal as an administrator (Windows) or prefix the command with sudo (macOS).

If you see a network error, check that you are connected to the internet and try again. Help me fix my npm install error.

Now you need to store your API key in a file that Node.js can read.

  • Switch back to Cursor.
  • Open the hci-design-assistant folder: click File in the top menu bar, then Open Folder.
  • Select the hci-design-assistant folder on your Desktop, then click Open.
  • Create a new file by clicking the new file icon in the Explorer sidebar, or pressing Cmd+N (macOS) or Ctrl+N (Windows).
  • Name the file .env and paste the following content into it:
ANTHROPIC_API_KEY=your-api-key-here
  • Replace your-api-key-here with the API key you copied from the Anthropic Console.
  • Save the file.

Why a .env file?

The .env file stores your API key outside of your code. This keeps secrets out of version control and makes it easy to swap keys between environments.

The Anthropic SDK automatically reads the ANTHROPIC_API_KEY environment variable when you create a client. You will use Node.js's built-in --env-file flag to load this file when running your scripts.

Verify the setup works with a test script

Before building the full application, confirm that your API key works and the SDK can reach Claude.

  • Create a new file in Cursor called test.js and paste the following code:
import Anthropic from "@anthropic-ai/sdk";

// Create a client (auto-reads ANTHROPIC_API_KEY from environment)
const anthropic = new Anthropic();

// Send a simple test message to Claude
const message = await anthropic.messages.create({
  model: "claude-haiku-4-5",
  max_tokens: 256,
  messages: [{ role: "user", content: "Say hello in one sentence." }],
});

// Log the response text
console.log(message.content[0].text);

What does this code do?

`import Anthropic from "@anthropic-ai/sdk"` loads the official SDK. The new Anthropic() constructor automatically picks up your ANTHROPIC_API_KEY from the environment.

`anthropic.messages.create()` sends a message to the claude-haiku-4-5 model and returns the response. The final line prints Claude's reply to your terminal.

  • Save test.js.
  • Return to your terminal and run the test script with your .env file loaded:
node --env-file=.env test.js

You should see a short greeting from Claude printed in your terminal, such as "Hello! How can I help you today?" or similar.

Seeing an authentication error?

If you see a message about an invalid API key, double check that your .env file has no spaces around the = sign and that the key is pasted correctly with no extra characters.

If you see "Cannot find module" or a syntax error, confirm that package.json has "type": "module" set. You can verify by opening package.json in Cursor and checking for the line.

Help me debug my Claude API test script.

  • Once you see a successful response, you can delete test.js. It was only needed to confirm your setup works.

✔️ Awesome, I've got everything!

Great. Double check you have saved your .env file and that node_modules exists in your project folder.

ⓧ I'd like to double check the full code

Here is what your .env file should look like:

ANTHROPIC_API_KEY=your-api-key-here

And here is what your package.json should look like (version numbers may differ slightly):

{
  "name": "hci-design-assistant",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "@anthropic-ai/sdk": "^0.106.0",
    "express": "^4.19.2"
  }
}

Your environment is wired up and Claude is responding. Next up, you will craft the system prompt that turns Claude into an HCI-savvy design engine.

Build the HCI System Prompt

Your project is set up and the Claude API is ready to go. Now comes the most important part of any AI-powered tool: teaching the AI what you actually want it to do.

A system prompt is a set of persistent instructions that shape every response the AI generates. In this step, you will encode decades of HCI research into a system prompt that forces Claude to generate designs grounded in real usability principles.

In this step, get ready to:
  • Create a system prompt file encoding Nielsen's heuristics, Fitts's Law, Norman's principles, and WCAG accessibility rules.
  • Structure the prompt to require JSON output with separate HTML and audit fields.
  • Test the prompt by generating a design for a signup form and verifying the output.
Create the system prompt file

The system prompt is where you encode domain expertise as rules the AI must follow. Instead of vague instructions like "make it usable," you will give Claude specific, measurable constraints drawn from established research.

  • In Cursor, create a new file called system-prompt.js in the root of your project folder (the same level as package.json).
  • Paste the following code into system-prompt.js. This is the first half, covering the role definition, output format, and the contrast/touch-target/semantics rules:
export const systemPrompt = `You are an HCI-grounded frontend design assistant. When given a plain-English description of a UI component, generate a complete HTML/CSS solution and a structured audit of your design decisions.

Respond ONLY with valid JSON in this exact shape, no preamble or markdown:
{
  "html": "<string: complete standalone HTML with inline <style>>",
  "audit": {
    "contrast": [
      { "element": "string", "foreground_hex": "#xxxxxx", "background_hex": "#xxxxxx", "ratio": number, "requirement": "4.5:1 or 3:1", "pass": boolean }
    ],
    "touch_targets": [
      { "element": "string", "width_px": number, "height_px": number, "spacing_px": number, "pass": boolean }
    ],
    "semantics": [
      { "element": "string", "tag_used": "string", "aria_attributes": "string or null" }
    ],
    "fitts_law": "string: explanation of primary action placement/size reasoning",
    "error_prevention": "string: explanation of inline validation approach",
    "heuristics_applied": ["string", "string", "..."]
  }
}

HARD RULES (do not treat these as suggestions — every value must be verifiable against the HTML you output):

1. CONTRAST
   - Body text (under 18px, or under 14px bold): minimum 4.5:1 contrast ratio
   - Large text (18px+ regular, or 14px+ bold): minimum 3:1 contrast ratio
   - Report the exact hex codes you used and the computed ratio for every text/background pairing in the "contrast" array
  • Continue in the same file, adding the remaining rules (touch targets, semantics, Fitts's Law, error prevention, and general constraints) below what you just pasted:

2. TOUCH TARGETS
   - Every interactive element (button, link, input, checkbox, etc.) must be at least 44x44px
   - Adjacent interactive elements must have at least 8px of spacing between their tap areas
   - List every interactive element in the "touch_targets" array

3. SEMANTIC HTML & ARIA
   - Use semantic elements (<nav>, <button>, <label>, <fieldset>, <main>, <header>) instead of generic <div>/<span> wherever a semantic equivalent exists
   - Any icon-only control must have aria-label or aria-describedby
   - Every form input must have an associated <label>

4. FITTS'S LAW
   - Primary actions must be sized larger than secondary actions
   - Primary actions must be placed within easy reach (bottom of viewport on mobile-oriented layouts, or top-right/inline with content flow on desktop-oriented layouts)
   - Explain your placement/size reasoning in "fitts_law"

5. ERROR PREVENTION
   - Form validation must use inline messaging (text + icon), never color alone, to indicate errors
   - Explain the validation pattern used in "error_prevention"

6. GENERAL CONSTRAINTS
   - No external stylesheets, fonts, or scripts — everything must be self-contained in the "html" string
   - The HTML must render correctly standalone inside an iframe via srcdoc
   - Keep output focused: do not add features, copy, or components beyond what the user described

If you cannot meet a requirement exactly (e.g., a contrast ratio you can't hit with the requested color scheme), adjust the design until you can — do not report a failing value as if it were acceptable.`;

What does this system prompt do?

This prompt has two key differences from a typical "make it accessible" instruction:

  • Structured audit schema — the output JSON includes typed arrays for contrast pairs, touch targets, and semantic elements. Each entry has measurable values (hex codes, ratios, pixel dimensions, pass/fail booleans) that your automated checker can verify against the actual rendered HTML.
  • Hard rules, not suggestions — the prompt explicitly tells Claude not to report failing values as acceptable. If a contrast ratio can't be met with the chosen palette, it must adjust the design rather than ship a violation.

The audit schema maps directly to what axe-core checks in Step 5. When Claude reports a contrast ratio of 5.2:1 for a specific element, your automated checker can measure the real value and flag any discrepancy.

  • Save system-prompt.js.

Seeing a syntax error highlight?

Make sure the file starts with export const systemPrompt = ` (a backtick, not a regular quote) and ends with `; (backtick then semicolon). Template literals use backticks to allow multi-line strings.

help me fix a syntax error in my system-prompt.js template literal.

Test the system prompt with a sample request

Before building the full server, you should verify the prompt actually produces the structured output you expect. A quick test script will confirm Claude returns valid HTML and a proper audit array.

  • Create a file called test-prompt.js in your project root with the following code:
// Temporary test script to verify the system prompt works
import Anthropic from "@anthropic-ai/sdk";
import { systemPrompt } from "./system-prompt.js";

const anthropic = new Anthropic();

const message = await anthropic.messages.create({
  model: "claude-haiku-4-5",
  max_tokens: 4096,
  system: systemPrompt,
  messages: [{ role: "user", content: "A signup form for a fitness app" }],
});

// Parse and display the structured response
const response = JSON.parse(message.content[0].text);
console.log("--- HTML Preview (first 200 chars) ---");
console.log(response.html.substring(0, 200));
console.log("\n--- Contrast Audit ---");
response.audit.contrast.forEach((c) => {
  console.log(`  ${c.element}: ${c.foreground_hex}/${c.background_hex} = ${c.ratio} (${c.pass ? "PASS" : "FAIL"})`);
});
console.log("\n--- Touch Targets ---");
response.audit.touch_targets.forEach((t) => {
  console.log(`  ${t.element}: ${t.width_px}x${t.height_px}px, spacing ${t.spacing_px}px (${t.pass ? "PASS" : "FAIL"})`);
});

What does this test script do?

It imports your system prompt and sends a test request asking Claude to design a signup form for a fitness app. Then it parses the JSON response and prints a preview of the HTML plus each audit item. This confirms the prompt produces the correct output structure before you build the full server.

  • Run the test script by executing this command in your terminal:
node --env-file=.env test-prompt.js

What does --env-file do?

The --env-file=.env flag tells Node.js to load the variables from your .env file into the environment before running the script. This is how the Anthropic SDK picks up your ANTHROPIC_API_KEY automatically.

After a few seconds, you should see output showing a snippet of HTML (starting with <!DOCTYPE html>) and a list of audit items with principle names like "Fitts's Law" or "Error Prevention."

Getting an authentication error?

Double-check that your .env file contains ANTHROPIC_API_KEY= followed by your actual key (no quotes around the value). Make sure there are no extra spaces.

If you see a JSON parse error, Claude may have returned markdown-wrapped JSON. Run the test again. The system prompt explicitly instructs "no markdown, no code fences" which usually works on retry.

help me debug my Claude API test script.

  • Once you have confirmed the test works, delete test-prompt.js. It was only needed to verify the system prompt.

✔️ Awesome, I've got everything!

Great. Double check you have saved system-prompt.js and that your test showed valid HTML plus audit items.

ⓧ I'd like to double check the full code

Here is the complete system-prompt.js file:

Scroll up and confirm your system-prompt.js contains both chunks in order. The first chunk starts with export const systemPrompt = ` and covers the role, JSON schema, and contrast rules. The second chunk covers touch targets, semantics, Fitts's Law, error prevention, and general constraints, ending with `;.

Your system prompt is locked in and verified. Next up, you will build the Express server that connects this prompt to a web interface so you can generate designs from a browser.

Build the Express Server

Your HCI system prompt is ready to guide Claude's design decisions. Now you need a server that accepts design requests from a browser, sends them to Claude with your prompt attached, and returns the generated HTML and audit data.

Express is a lightweight Node.js web framework that makes it simple to define API endpoints and serve static files. In this step, you'll create the server that ties your system prompt to the Claude API and exposes everything through a clean endpoint.

In this step, get ready to:
  • Set up an Express server with static file serving and JSON parsing.
  • Create an API endpoint that sends requests to Claude with the HCI system prompt.
  • Add error handling and start the server on port 3000.
Set up Express and the Anthropic client

The server needs three capabilities: parsing incoming JSON requests, serving your frontend files, and connecting to the Claude API. You'll configure all three in the first section of the file.

  • Create a new file called server.js in the root of your project folder (the same level as package.json).
  • Add the following code to set up the Express app and Anthropic client:
import Anthropic from "@anthropic-ai/sdk";
import express from "express";
import { systemPrompt } from "./system-prompt.js";

// Create the Express app and configure middleware
const app = express();
app.use(express.json());
app.use(express.static("public"));

// Initialize the Anthropic client (reads ANTHROPIC_API_KEY from environment)
const anthropic = new Anthropic();

What does this code do?

  • express.json() parses incoming request bodies as JSON, so your endpoint can read the design description from the browser.
  • express.static("public") serves any files in a public folder automatically. This is where your frontend HTML, CSS, and JavaScript will live.
  • new Anthropic() creates the API client. It automatically reads your ANTHROPIC_API_KEY from the environment, so you don't need to pass it explicitly.
  • Save server.js.
Add the /api/generate endpoint

This is the core of your server. When the frontend sends a design description, this endpoint wraps it in a message to Claude along with your HCI system prompt, then returns the structured response.

  • Below the const anthropic = new Anthropic(); line, add the following route handler:
app.post("/api/generate", async (req, res) => {
  const { description, focus } = req.body;

  // Validate that a description was provided
  if (!description || description.trim() === "") {
    return res.status(400).json({ error: "Description is required." });
  }

  // Build the user message from the description
  let userMessage = `Design the following UI component: ${description}`;

  // If a focus principle is specified, instruct Claude to emphasize it
  if (focus) {
    userMessage += `\n\nIMPORTANT: While applying all HCI principles, EMPHASIZE "${focus}" above all others. Push this principle further than the baseline. In your audit, include at least 2 entries showing how you went beyond the default application of this principle.`;
  }

  try {
    // Send the request to Claude with the HCI system prompt
    const message = await anthropic.messages.create({
      model: "claude-haiku-4-5",
      max_tokens: 4096,
      system: systemPrompt,
      messages: [{ role: "user", content: userMessage }],
    });

    // Parse Claude's JSON response
    const responseText = message.content[0].text;
    const parsed = JSON.parse(responseText);

    // Return the HTML design and HCI audit to the client
    res.json({ html: parsed.html, audit: parsed.audit });
  • Immediately below that code, add the error handling that closes the endpoint:
  } catch (error) {
    console.error("API Error:", error.message);

    // Handle authentication failures
    if (error.message.includes("authentication")) {
      return res.status(401).json({ error: "Invalid API key. Check your .env file." });
    }

    // Handle JSON parse failures from unexpected Claude output
    if (error instanceof SyntaxError) {
      return res.status(500).json({ error: "Failed to parse AI response as JSON. Try again." });
    }

    // Handle all other errors
    res.status(500).json({ error: "Failed to generate design. Please try again." });
  }
});

What does this endpoint do?

The endpoint handles the full lifecycle of a design request:

  • It validates that the request includes a non-empty description field, rejecting empty submissions with a 400 error.
  • It builds a user message and optionally appends instructions to emphasize a specific HCI principle (the focus parameter). You'll use this feature later to explore how individual principles reshape a design.
  • It calls anthropic.messages.create() with your system prompt and the user's description, then parses Claude's JSON response into separate html and audit fields.
  • The catch block handles three failure modes: invalid API keys, unexpected non-JSON responses from Claude, and general API errors.
  • Save server.js.

Seeing a red underline on the template literal?

The userMessage string uses backticks (`) for template literals, not regular quotes. Make sure you're using the backtick character (typically the key to the left of 1 on your keyboard).

help me fix a template literal syntax error in my server.js

Start the server and test

The final piece is starting the server so it listens for incoming requests on a specific port.

  • Add the following at the bottom of server.js:
// Start the server on port 3000
const PORT = 3000;
app.listen(PORT, () => {
  console.log(`HCI Design Assistant running at http://localhost:${PORT}`);
});

What does this code do?

app.listen() starts the Express server and binds it to port 3000. Once running, any HTTP request to http://localhost:3000 hits your server. The callback logs a confirmation message so you know it started successfully.

  • Save server.js.
  • Start your server by running this command in your terminal:
node --env-file=.env server.js

Why --env-file=.env?

The Anthropic SDK automatically reads the ANTHROPIC_API_KEY environment variable, but it needs that variable to exist in your terminal session. The --env-file=.env flag tells Node.js to load your .env file into the environment before your code runs. No extra packages needed.

  • You should see this message in your terminal:
HCI Design Assistant running at http://localhost:3000

The server is now running and waiting for requests. You don't have a frontend yet, so visiting http://localhost:3000 in your browser will show a blank page or a "Cannot GET /" message. That's expected.

Server not starting?

  • If you see an error about ANTHROPIC_API_KEY, double-check that your .env file is in the same folder as server.js and contains your actual API key (not the placeholder text).
  • If you see "Cannot find module", make sure system-prompt.js exists in the same directory and uses export const systemPrompt (not module.exports).
  • If you see "address already in use", another process is using port 3000. Stop it or change the PORT value in server.js.

help me debug why my Express server won't start

  • Stop the server by pressing Ctrl+C in your terminal. You'll restart it again once the frontend is ready.

✔️ Awesome, I've got everything!

Great. Make sure you've saved server.js before moving on.

ⓧ I'd like to double check the full code

Here is the complete server.js file:

Scroll up and confirm your server.js contains all three chunks in order: the imports and middleware setup, the /api/generate POST endpoint with error handling, and the app.listen() server startup at the bottom.

Your Express server is wired up and ready to handle design requests. Next up, you'll build the frontend interface that lets you type a description and see Claude's generated design live in the browser.

Build the Frontend Interface

Your server is ready and waiting for requests. Now it needs a face.

In this step, you'll build the browser interface that lets users describe a UI component, sends that description to your server, and displays both the generated design and its HCI audit side by side.

In this step, get ready to:
  • Create the HTML layout with an input panel and split output panels.
  • Style the interface using CSS custom properties and grid layout.
  • Wire up JavaScript to call the API and render results.
Create the HTML structure

The interface uses a two-column layout. The left panel holds the input controls. The right panel splits vertically between a live preview and an HCI audit section.

  • Create a new file called index.html inside your public folder by running this command from your project root:
touch public/index.html
  • Open public/index.html in Cursor and paste the following code:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>HCI Design Assistant</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <header>
    <h1>HCI Design Assistant</h1>
    <p>Generate frontend designs grounded in Human-Computer Interaction research</p>
  </header>

  <main>
    <section class="input-panel" aria-label="Design input">
      <label for="description">Describe your UI component</label>
      <textarea
        id="description"
        placeholder="e.g., A login page for a meditation app with email and password fields"
        rows="5"
      ></textarea>

      <label for="focus">Emphasize a principle (optional)</label>
      <select id="focus">
        <option value="">All principles equally</option>
        <option value="Fitts's Law">Fitts's Law</option>
        <option value="Accessibility (WCAG)">Accessibility (WCAG)</option>
        <option value="Error Prevention">Error Prevention</option>
      </select>
  • Immediately below that code in the same file, add the remaining select options, button, output panel, and closing tags:
        <option value="Aesthetic and Minimalist Design">Minimalist Design</option>
        <option value="Norman's Affordances and Feedback">Affordances and Feedback</option>
      </select>

      <button id="generate-btn" type="button">Generate Design</button>
      <div id="status" class="status" aria-live="polite"></div>
    </section>

    <section class="output-panel" aria-label="Design output">
      <div class="preview-container">
        <h2>Live Preview</h2>
        <iframe id="preview" title="Generated design preview"></iframe>
      </div>

      <div class="audit-container">
        <h2>HCI Audit</h2>
        <div id="audit-list"></div>
      </div>
    </section>
  </main>

  <script src="script.js"></script>
</body>
</html>

What does this HTML do?

The page is divided into two <section> elements inside <main>. The left section (input-panel) contains a textarea for describing the UI, a select dropdown for emphasizing a specific HCI principle, and a button to trigger generation.

The right section (output-panel) has an iframe for rendering the generated HTML preview and a div for displaying HCI audit cards. The aria-live="polite" attribute on the status div ensures screen readers announce status changes.

💡 Why use an iframe for the preview?

The iframe's srcdoc property lets you inject a complete HTML document into an isolated rendering context. The generated design's styles won't bleed into your app's styles, and your app's styles won't affect the preview.

  • Save public/index.html.
  • Open your browser to http://localhost:3000. You should see unstyled text, a textarea, a dropdown, and a button.

Seeing a blank page?

Make sure your server is still running in the terminal. If you stopped it, start it again with node --env-file=.env server.js. Also confirm the file is saved inside the public folder, not the project root.

Help me debug why my page isn't loading at localhost:3000.

Style the interface with CSS

Your interface applies HCI principles to itself. The button meets Fitts's Law with a 48px minimum height, focus styles satisfy WCAG, and the visual hierarchy uses deliberate spacing and color.

  • Create public/style.css by running:
touch public/style.css
  • Open public/style.css and paste the foundation styles, including CSS custom properties, reset, body, header, grid layout, and form inputs:
:root {
  --color-bg: #f8f9fa;
  --color-surface: #ffffff;
  --color-primary: #2563eb;
  --color-primary-hover: #1d4ed8;
  --color-text: #1f2937;
  --color-text-muted: #6b7280;
  --color-border: #e5e7eb;
  --color-success: #059669;
  --color-error: #dc2626;
  --radius: 8px;
  --shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--color-bg);
  color: var(--color-text);
  line-height: 1.6;
  min-height: 100vh;
}
  • Continue in the same file, adding the header, grid layout, and input panel styles:
header h1 {
  font-size: 1.5rem;
  font-weight: 700;
  margin-bottom: 4px;
}

header p {
  color: var(--color-text-muted);
  font-size: 0.875rem;
}

main {
  display: grid;
  grid-template-columns: 380px 1fr;
  gap: 0;
  height: calc(100vh - 85px);
}

.input-panel {
  padding: 24px;
  background: var(--color-surface);
  border-right: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  gap: 12px;
  overflow-y: auto;
}

What does this CSS do?

The :root block defines a design token system using CSS custom properties. Every color, radius, and shadow is stored as a variable so the entire palette stays consistent.

The grid-template-columns: 380px 1fr on main creates the two-column layout. The left panel is a fixed 380px sidebar, and the right panel takes the remaining space.

  • Below the code you just pasted, add the button, status, output panel, and audit card styles:
#generate-btn {
  padding: 14px 24px;
  background: var(--color-primary);
  color: white;
  border: none;
  border-radius: var(--radius);
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  min-height: 48px;
  transition: background 0.15s ease;
  margin-top: 8px;
}

#generate-btn:hover {
  background: var(--color-primary-hover);
}

#generate-btn:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

#generate-btn:disabled {
  background: var(--color-text-muted);
  cursor: not-allowed;
}

Which HCI principles does this CSS apply?

The button has min-height: 48px to satisfy Fitts's Law (large targets for primary actions). The :focus-visible outline meets WCAG visible focus requirements. The :disabled state uses Norman's constraint principle to prevent invalid actions.

The status element uses color coding (blue for loading, green for success, red for error) to satisfy Nielsen's "Visibility of System Status" heuristic.

  • Save public/style.css and refresh your browser.
  • You should see a clean two-column layout with a white sidebar on the left containing a textarea, dropdown, and blue button. The right side should show two labeled sections stacked vertically.

Layout looks wrong or unstyled?

Check that your <link> tag in index.html references style.css (not styles.css or another name). Both files must be in the public folder.

Help me fix my CSS layout for the HCI Design Assistant.

✔️ Awesome, I've got everything!

Great. Make sure you have saved public/style.css before continuing.

ⓧ I'd like to double check the full code

Here is the complete public/style.css file:

Scroll up and confirm your public/style.css contains all style chunks in order: CSS variables and reset, header and grid layout, form controls, button styles, status classes, output panel layout, preview container, audit container, and audit card styles.

Add the JavaScript logic

The JavaScript handles three jobs: sending the user's description to the server, rendering the returned HTML in the iframe, and displaying the audit as styled cards.

  • Create public/script.js by running:
touch public/script.js
  • Open public/script.js and paste the DOM references and event listeners:
// Cache DOM element references
const descriptionInput = document.getElementById("description");
const focusSelect = document.getElementById("focus");
const generateBtn = document.getElementById("generate-btn");
const statusEl = document.getElementById("status");
const previewIframe = document.getElementById("preview");
const auditList = document.getElementById("audit-list");

// Trigger generation on button click
generateBtn.addEventListener("click", handleGenerate);

// Support Ctrl+Enter as a keyboard shortcut
descriptionInput.addEventListener("keydown", (e) => {
  if (e.key === "Enter" && e.ctrlKey) {
    handleGenerate();
  }
});

What does this code do?

Caching DOM references at the top avoids repeated getElementById calls every time the user clicks the button. The Ctrl+Enter shortcut satisfies Nielsen's "Flexibility and Efficiency of Use" heuristic by providing a power-user shortcut.

  • Below the event listeners, add the main handleGenerate function that calls the API:
async function handleGenerate() {
  const description = descriptionInput.value.trim();
  if (!description) {
    setStatus("Please enter a description.", "error");
    return;
  }
  const focus = focusSelect.value;
  // Disable button and show loading state
  generateBtn.disabled = true;
  setStatus("Generating design...", "loading");
  auditList.innerHTML = "";
  previewIframe.srcdoc = "";
  try {
    const response = await fetch("/api/generate", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ description, focus }),
    });
    const data = await response.json();
    if (!response.ok) throw new Error(data.error || "Generation failed.");
    // Render the generated design and audit
    previewIframe.srcdoc = data.html;
    renderAudit(data.audit);
    setStatus("Design generated successfully.", "success");
  } catch (error) {
    setStatus(error.message, "error");
  } finally {
    generateBtn.disabled = false;
  }
}

What does handleGenerate do?

This function validates the input, disables the button during the request (Norman's constraint principle), and sends a POST to /api/generate with the description and optional focus principle.

On success, it sets previewIframe.srcdoc = data.html to inject the AI-generated HTML into the iframe as an isolated page, then calls renderAudit(data.audit) to populate the audit panel with cards for each finding. The finally block re-enables the button regardless of whether the request succeeded or failed.

  • Below handleGenerate, add the helper functions that render the audit cards and manage the status display:
// Render each audit section as individual cards
function renderAudit(audit) {
  auditList.innerHTML = "";
  if (!audit) { auditList.innerHTML = "<p>No audit data available.</p>"; return; }
  (audit.contrast || []).forEach((c) => {
    addCard("Contrast", c.element, `${c.foreground_hex} on ${c.background_hex} — ${c.ratio}:1 (need ${c.requirement})`, c.pass);
  });
  (audit.touch_targets || []).forEach((t) => {
    addCard("Touch Target", t.element, `${t.width_px}×${t.height_px}px, spacing ${t.spacing_px}px`, t.pass);
  });
  (audit.semantics || []).forEach((s) => {
    addCard("Semantics", s.element, `<${s.tag_used}>${s.aria_attributes ? " — " + s.aria_attributes : ""}`, null);
  });
  if (audit.fitts_law) addCard("Fitts's Law", null, audit.fitts_law, null);
  if (audit.error_prevention) addCard("Error Prevention", null, audit.error_prevention, null);
}

// Build a single audit card DOM element and append it
function addCard(principle, element, detail, pass) {
  const card = document.createElement("div");
  card.className = "audit-card" + (pass === true ? " pass" : pass === false ? " fail" : "");
  const el = element ? `<div class="element">${escapeHtml(element)}</div>` : "";
  const badge = pass !== null ? `<div class="badge ${pass ? "pass" : "fail"}">${pass ? "PASS" : "FAIL"}</div>` : "";
  card.innerHTML = `<div class="principle">${escapeHtml(principle)}</div>${el}<div class="detail">${escapeHtml(detail)}</div>${badge}`;
  auditList.appendChild(card);
}

function setStatus(msg, type) { statusEl.textContent = msg; statusEl.className = `status ${type}`; }

function escapeHtml(text) { const d = document.createElement("div"); d.textContent = text || ""; return d.innerHTML; }

What do these helper functions do?

The renderAudit function walks through each section of the structured audit object (contrast pairs, touch targets, semantics, Fitts's Law, error prevention) and creates a card for each entry. It delegates card creation to addCard, which builds the DOM element with a principle label, optional element name, detail text, and a PASS/FAIL badge for items that have a boolean result.

The escapeHtml function prevents any HTML in the AI's response from being interpreted as markup. It uses the browser's built-in text encoding by setting textContent and reading back innerHTML.

  • Save public/script.js and refresh your browser.
  • Click Generate Design without typing anything. You should see a red "Please enter a description." status message appear below the button.

No error message appearing?

Open the browser's DevTools console (F12 or Cmd+Option+I on macOS). If you see a ReferenceError, check that all three code sections are pasted in order with no typos in function names. The event listener references handleGenerate which must be defined below it.

Help me fix my script.js errors in the HCI Design Assistant.

✔️ Awesome, I've got everything!

Great. Make sure you have saved public/script.js before continuing.

ⓧ I'd like to double check the full code

Here is the complete public/script.js file:

Scroll up and confirm your public/script.js contains all three chunks in order: DOM references with event listeners, the handleGenerate function, and the helper functions (renderAudit, setStatus, escapeHtml).

Add a demo mode for API-free exploration

Demo mode lets you (or anyone without an API key) explore the full interface using a pre-recorded response. This makes it easy to understand how the preview and audit panel work before signing up for Anthropic.

  • In public/index.html, find the <button id="generate-btn"> line. Directly above it, add this checkbox:
<label class="demo-toggle">
  <input type="checkbox" id="demo-mode">
  Demo mode (no API key needed)
</label>
  • In public/script.js, add the following demo data constant at the very top of the file, before the DOM references:
// Pre-recorded demo response for API-free exploration
const DEMO_RESPONSE = {
  html: `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><style>*{box-sizing:border-box;margin:0;padding:0}body{font-family:system-ui,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#f3f4f6}.card{background:#fff;padding:32px;border-radius:12px;box-shadow:0 4px 12px rgba(0,0,0,.1);width:100%;max-width:400px}h1{font-size:1.5rem;margin-bottom:8px}.subtitle{color:#6b7280;margin-bottom:24px;font-size:.875rem}label{display:block;font-size:.875rem;font-weight:600;margin-bottom:4px}input{width:100%;padding:12px;border:1px solid #d1d5db;border-radius:8px;font-size:1rem;margin-bottom:16px}input:focus{outline:2px solid #2563eb;outline-offset:2px}button{width:100%;padding:14px;background:#2563eb;color:#fff;border:none;border-radius:8px;font-size:1rem;font-weight:600;cursor:pointer;min-height:48px}button:hover{background:#1d4ed8}button:focus-visible{outline:2px solid #2563eb;outline-offset:2px}</style></head><body><main class="card"><h1>Join FitTrack</h1><p class="subtitle">Start your fitness journey today</p><form><label for="name">Full name</label><input id="name" type="text" required placeholder="Jane Smith"><label for="email">Email</label><input id="email" type="email" required placeholder="jane@example.com"><label for="pw">Password</label><input id="pw" type="password" required minlength="8" placeholder="At least 8 characters"><button type="submit">Create Account</button></form></main></body></html>`,
  audit: {
    contrast: [
      { element: "h1", foreground_hex: "#111827", background_hex: "#ffffff", ratio: 15.4, requirement: "4.5:1", pass: true },
      { element: "button", foreground_hex: "#ffffff", background_hex: "#2563eb", ratio: 4.9, requirement: "4.5:1", pass: true }
    ],
    touch_targets: [
      { element: "input[type=text]", width_px: 400, height_px: 44, spacing_px: 16, pass: true },
      { element: "button[type=submit]", width_px: 400, height_px: 48, spacing_px: 16, pass: true }
    ],
    semantics: [
      { element: "form", tag_used: "form", aria_attributes: null },
      { element: "input#email", tag_used: "input", aria_attributes: "label[for=email]" }
    ],
    fitts_law: "Submit button is full-width (400px) with 48px height, maximizing target area for the primary action.",
    error_prevention: "Email field uses type=email for browser validation; password enforces minlength=8 with a placeholder hint.",
    heuristics_applied: ["Visibility of System Status", "Error Prevention", "Recognition Rather Than Recall"]
  }
};
  • Still in public/script.js, find the line const focus = focusSelect.value; inside handleGenerate. Directly below it, add this demo mode check:
  // If demo mode is active, use pre-recorded data instead of the API
  const demoMode = document.getElementById("demo-mode").checked;
  if (demoMode) {
    previewIframe.srcdoc = DEMO_RESPONSE.html;
    renderAudit(DEMO_RESPONSE.audit);
    setStatus("Demo mode — showing a pre-recorded example.", "success");
    return;
  }

What does demo mode do?

When the checkbox is checked, clicking Generate Design skips the API call entirely and renders a pre-recorded signup form with a full structured audit. This lets you explore the preview iframe and audit panel without needing an API key.

The demo audit mirrors the exact schema Claude returns: two contrast pairs with hex codes and ratios, two touch-target measurements, two semantic-HTML entries, plus text explanations for Fitts's Law and error prevention. Each card type renders the same way it would from a live API response.

  • Save both public/index.html and public/script.js.
  • Refresh your browser. You should see a new Demo mode checkbox above the Generate Design button.
  • Check the Demo mode box, type anything in the textarea, and click Generate Design. A signup form preview appears instantly with eight audit cards below it (two contrast, two touch targets, two semantics, plus Fitts's Law and error prevention).

Your frontend is built and connected. Next up, you'll test the full pipeline end-to-end by generating real designs and reviewing the HCI audit output.

Test and Refine

Your HCI Design Assistant has an Express backend, a Claude integration with a detailed system prompt, and a split-panel frontend ready to display results.

Now it's time to put the tool through its paces. Testing with varied prompts reveals how well your system prompt translates HCI principles into concrete design decisions.

In this step, get ready to:
  • Test the tool end-to-end with three different UI prompts.
  • Review generated designs against the HCI audit panel.
  • Refine the system prompt based on testing findings.
Test with three different prompts

Each prompt exercises different aspects of the HCI principles in your system prompt. A login form tests form elements and CTA placement. A pricing table tests layout and visual hierarchy. An error page tests recovery messaging and clear communication.

  • Return to your browser at http://localhost:3000.
  • Type the following into the description textarea:
A login page for a meditation app
  • Leave the Emphasize a principle dropdown set to All principles equally.
  • Click Generate Design.

You should see the status change to "Generating design..." in blue text. After a few seconds, a rendered login page appears in the Live Preview iframe and audit cards populate the HCI Audit section below it.

Seeing an error instead of a design?

If you see "Invalid API key", double-check that your ANTHROPIC_API_KEY environment variable is set correctly in the terminal session running your server.

If you see "Failed to parse AI response as JSON", the model occasionally returns malformed JSON. Click Generate Design again to retry.

Help me debug my HCI Design Assistant API errors

  • Clear the textarea and enter the second test prompt:
A pricing table with 3 tiers
  • Click Generate Design and wait for the result.

This prompt tests how the model handles layout, visual hierarchy, and comparison between options. You should see a multi-column layout with clear differentiation between tiers.

  • Clear the textarea once more and enter the third test prompt:
An error page for a 404 not found
  • Click Generate Design and wait for the result.

This prompt tests error recovery principles. The generated page should include a clear explanation of what went wrong and a prominent CTA to navigate back to safety.

Why these three prompts?

Each prompt exercises a different cluster of HCI principles. The login form stresses error prevention and form accessibility. The pricing table stresses visual hierarchy and recognition over recall. The 404 page stresses error recovery and user control.

Testing with diverse prompts exposes gaps in your system prompt that a single test would miss.

Review the generated designs against the HCI audit

The HCI Audit panel shows which principles Claude claims to have applied. Your job now is to verify those claims against the actual rendered output.

  • Pick one of your generated designs and look at the audit cards in the lower-right panel.
  • For each audit card, check whether the generated HTML in the Live Preview actually implements what the card claims.
  • Right-click the Live Preview iframe and select Inspect to open DevTools on the generated HTML.

Here are specific things to verify in the generated designs:

  • Check that primary buttons are actually 44px or taller by inspecting their computed height in DevTools.
  • Look for semantic HTML elements like <form>, <label>, <nav>, and <main> instead of generic <div> wrappers.
  • Verify that form inputs have matching for/id attributes connecting labels to inputs.
  • Check that hover and focus states are defined in the embedded CSS by searching for :hover and :focus in the Elements panel.

What if the audit claims don't match the design?

This is expected and common. LLMs can claim to apply a principle in the audit without actually implementing it in the code. These gaps are exactly what you will address in the next substep by refining your system prompt.

Add automated accessibility checking

Claude's audit panel tells you what it claims to have done. But LLMs can confidently state they applied a principle without actually implementing it in the code. An automated check measures the real output.

You will use axe-core (the same engine behind browser accessibility extensions) to scan the generated HTML for actual contrast failures and undersized touch targets. Violations appear in the audit panel as red cards, right alongside Claude's self-reported cards.

  • In public/index.html, find the <script src="script.js"></script> line near the bottom. Directly above it, add the axe-core CDN script:
<script src="https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.10.2/axe.min.js"></script>

What is axe-core?

axe-core is a lightweight accessibility testing engine. Loading it from a CDN means no npm install needed. It exposes a global axe object you can run against any DOM, including an iframe's document.

  • In public/script.js, add the following function at the bottom of the file, after escapeHtml:
async function runAccessibilityCheck() {
  // Wait for the iframe to finish rendering
  await new Promise((resolve) => {
    previewIframe.addEventListener("load", resolve, { once: true });
  });

  const iframeDoc = previewIframe.contentDocument;
  if (!iframeDoc || !window.axe) return;

  // Inject axe-core into the iframe and run it
  const results = await axe.run(iframeDoc, {
    rules: {
      "color-contrast": { enabled: true },
      "target-size": { enabled: true },
      "label": { enabled: true },
      "link-name": { enabled: true },
      "button-name": { enabled: true },
    },
  });

  renderViolations(results.violations);
}
  • Below runAccessibilityCheck, add the function that renders violations as red cards in the audit panel:
function renderViolations(violations) {
  if (violations.length === 0) {
    const pass = document.createElement("div");
    pass.className = "audit-card pass-card";
    pass.innerHTML = `<div class="principle">Automated Check</div>
      <div class="application">All axe-core checks passed.</div>`;
    auditList.appendChild(pass);
    return;
  }

  // Add a violation card for each failure
  violations.forEach((v) => {
    const card = document.createElement("div");
    card.className = "audit-card violation-card";
    card.innerHTML = `
      <div class="principle">VIOLATION: ${escapeHtml(v.id)}</div>
      <div class="application">${escapeHtml(v.description)}</div>
      <div class="element">${escapeHtml(v.nodes[0]?.html || "")}</div>`;
    auditList.appendChild(card);
  });
}

What do these functions do?

The runAccessibilityCheck function waits for the iframe to load, then runs axe-core against its document. It checks five key rules: color contrast (WCAG 4.5:1), target size (44px minimum), label presence, and named links/buttons.

The renderViolations function appends results after Claude's audit cards. A green "all passed" card appears when there are no violations. Red violation cards show the specific failing rule, a description, and the offending HTML element.

  • Still in public/script.js, find the line setStatus("Design generated successfully.", "success"); inside handleGenerate. Directly above it, add this call:
    // Run automated accessibility checks against the rendered HTML
    await runAccessibilityCheck();
  • Finally, add the violation card styles. In public/style.css, add the following at the very bottom of the file:
.violation-card {
  border-left: 3px solid var(--color-error);
  background: #fef2f2;
}

.violation-card .principle {
  color: var(--color-error);
}

.pass-card {
  border-left: 3px solid var(--color-success);
  background: #f0fdf4;
}

.pass-card .principle {
  color: var(--color-success);
}
  • Save public/index.html, public/script.js, and public/style.css.
  • Restart your server (Ctrl+C then node --env-file=.env server.js).
  • Return to your browser at http://localhost:3000 and generate a design. After the preview loads, scroll down in the HCI Audit panel. Below Claude's self-reported cards you should see either a green "All axe-core checks passed" card or one or more red violation cards.

Compare what Claude claimed in its audit cards to what axe-core actually found. If axe-core reports a contrast failure that Claude's audit says it handled, you have proof the LLM's self-report was inaccurate. This is exactly the kind of gap your system prompt refinements should target.

Not seeing any automated check results?

If you see no violation cards and no green pass card, open DevTools (F12) and check the console. A SecurityError about cross-origin frames means the iframe's srcdoc content is not loading in the same origin. Confirm you are using previewIframe.srcdoc (not src with a URL).

If axe is undefined, verify the CDN script tag appears before your script.js tag in the HTML.

Help me debug why axe-core isn't running against my iframe.

Test keyboard navigation and screen reader access

Automated tools catch measurable failures, but they miss experiential problems. A form can pass every axe-core rule and still feel broken to a keyboard-only user if focus jumps unpredictably or states are invisible. This quick manual check catches what machines cannot.

  • Generate a fresh login form design (or use one you already have in the Live Preview).
  • Click inside the Live Preview iframe to give it focus.
  • Press Tab repeatedly to move through every interactive element in the generated design. Watch for a visible focus ring or outline on each element as you land on it.
  • Press Shift+Tab to move backwards. Confirm you can reach every input, link, and button in both directions.
  • On any focused button, press Enter or Space to confirm it activates.

Ask yourself: did every interactive element show a visible focus indicator? Was the tab order logical (top to bottom, left to right)? If any element was invisible when focused or the order jumped unexpectedly, that is a real accessibility failure your system prompt should address.

Extra for Experts: Test with a screen reader

For an even deeper test, try your generated design with a real screen reader. On macOS, press Cmd+F5 to toggle VoiceOver. On Windows, press Ctrl+Win+Enter to start Narrator, or download NVDA (free).

Listen for whether form labels are announced when you focus each input. If the screen reader says "edit text" without a label name, the generated HTML is missing a <label> association. If buttons are announced as just "button" with no name, an aria-label or visible text is missing. These are exactly the failures Claude's audit might claim it prevented.

Refine the system prompt based on testing

System prompt engineering is iterative. Your first test run exposed which rules the model follows reliably and which it ignores or implements weakly.

  • Switch back to system-prompt.js in Cursor.
  • Review the areas where your generated designs fell short and apply refinements to the relevant sections of the prompt.

Here are common refinements based on typical testing findings:

  • If spacing feels inconsistent, add specific pixel values to your spacing rules (e.g., "use 8px, 16px, 24px, or 32px for all spacing, never arbitrary values").
  • If typography lacks hierarchy, add a scale rule (e.g., "use a typographic scale: 14px body, 18px subheadings, 24px headings, 32px page title").
  • If hover/focus states are missing, strengthen the feedback requirement (e.g., "every button and link MUST define :hover, :focus-visible, and :active states with visible changes").
  • If the color palette feels generic, add a rule requiring CSS custom properties with at least a primary, secondary, surface, and error color.

Concrete beats abstract

LLMs produce better design output when given measurable constraints. "Use adequate spacing" is vague. "Maintain 16px minimum spacing between interactive elements and 24px between form groups" is actionable.

  • Save system-prompt.js after making your changes.
  • Restart your server by pressing Ctrl+C in the terminal, then running:
node --env-file=.env server.js
  • Return to your browser and re-run one of the three test prompts.
  • Compare the new output to what you saw before. The refined prompt should produce designs that address the gaps you identified.

Why restart the server?

Your system prompt is imported at server startup. Changes to system-prompt.js only take effect after the server restarts and re-imports the module.

🙋‍♀️ Design quality not improving?

Make sure you restarted the server after saving your changes. If quality is still the same, try making your rules more specific with exact pixel values or explicit CSS property names.

Help me improve my HCI system prompt for better design output

Extra for Experts: Want a permanently free local alternative?

After completing this project with Claude, you can swap in a local model via Ollama for a permanently free setup with no API key, no SMS verification, and no expiration. The tradeoff is output quality. Local 7B–13B models produce less consistent structured JSON and weaker HTML/CSS than Claude Haiku 4.5.

To try it: install Ollama from ollama.com, pull a model (e.g. ollama pull qwen3:8b), install the OpenAI client (npm install openai), then replace the Anthropic SDK call in server.js with the OpenAI-compatible endpoint at http://localhost:11434/v1/ using model qwen3:8b. Ollama ignores the API key field, so you can pass any string.

Keep your system prompt identical. The HCI principles still guide the output, but you may need to simplify the JSON schema or retry failed parses more often with smaller models.

Secret mission

Add "Redesign with Focus" Mode

Your design assistant applies all HCI principles equally. But what if you could crank one principle up to maximum? In this mission, you will explore the Redesign with Focus feature that lets users emphasize a single HCI principle and compare how it reshapes the same design.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs from leaving it on your machine. API calls to Claude are pay-as-you-go and only incur charges when you actively use the app.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Anthropic Console account with API key (free credits expire after 14 days).
  • Local Node.js project folder (hci-design-assistant) with installed dependencies.

Keep everything running

No action needed. Choose this if you are still actively building or experimenting with new prompts.

  • Your project files stay on your Desktop.
  • Your API key remains active in the Anthropic Console. You will only be charged when you make API calls.
  • Claude Haiku 4.5 costs $1 per million input tokens and $5 per million output tokens. A typical generation uses roughly 2000 tokens total, costing less than $0.01 per request.

Pause - I'll come back to this later

Stop the running server to free up your terminal. Your files and API key stay intact for next time.

  • In your terminal, press Ctrl+C to stop the Express server.
  • Your project folder and .env file remain on disk, ready for you to resume with npm start.

Delete - I don't want to use this again

Remove all project resources and revoke your API key so nothing lingers.

  • In your terminal, press Ctrl+C to stop the server if it is still running.
  • Navigate to your Desktop and delete the project folder by running:

macOS

cd ~/Desktop
rm -rf hci-design-assistant

Windows

cd ~\Desktop
Remove-Item -Recurse -Force hci-design-assistant
  • To revoke your API key, open console.anthropic.com in your browser and navigate to the API Keys section. Find the key you created for this project and disable or delete it.

Nice Work!

Nice Work!

You just built a full-stack AI design tool that generates accessible, usability-focused frontend code from natural language descriptions. Every design it produces is grounded in decades of Human-Computer Interaction research.

Here's what you accomplished:

  • Built a full-stack web application powered by the Claude API, with an Express server proxying requests to Anthropic's Messages API.
  • Crafted a system prompt that encodes 10+ HCI principles (Nielsen's heuristics, Fitts's Law, Norman's design principles, WCAG accessibility) as actionable design rules for structured JSON output.
  • Created a live preview system using iframe srcdoc and an HCI audit panel that makes AI reasoning transparent and educational.
  • Secret Mission: Added a "Redesign with Focus" mode that lets users select a specific HCI principle to emphasize, exploring how individual principles reshape a design.

Ready to quiz yourself?