Design Engineer Application

Subhaprada Chand — Unkey

I have spent around 5 days on this application. Started by running the full dev stack, creating real APIs and keys, and understanding the product as a developer would. Then focused on redesigning the parts that needed it.

Before this, I built Nurturli from scratch — design, code, user research, shipping. I work in code, iterate in the browser, and reach for Figma when the team needs alignment.

The Approach

I picked two surfaces that shape first impressions — the marketing site and the Create New Key flow in the dashboard.

On the marketing side, I focused on the hero and the navigation. I redesigned the hero layout, introduced Clash Display typography, built a cyan accent system, and simplified the nav from 7 competing links to a focused hierarchy with a Product dropdown.

In the dashboard, I rethought the key creation experience. Added a live SDK code preview that updates as you fill the form, improved inactive field guidance so disabled fields point you to the right toggle, and brought visual consistency between the marketing site and the product.

Design Decisions

Every choice is documented — what was tried, what was rejected, and why. Click any card to see the full breakdown with before/after screenshots.

Dashboard
Marketing
How I Design

I design in code.

Code is the output anyway, so that's where I design. With AI-assisted tools, iteration at the code level is fast enough that going back to Figma is rarely necessary. I get a design vision into the browser, use the actual design, play around with it, and iterate directly in code. This also cuts down on any handoffs or translations between design and code. What is being designed is what ships.

I use tools like Storybook to design.

I build new components and design languages in isolation. It's where I define how something looks and behaves before it touches the rest of the app. It gives me a focused space to explore states, variants, and interactions without the noise of a full page around it.

I write down what didn't work.

Not as a formality. I genuinely find it useful to come back to "why did we reject this?" three weeks later. This whole application is built on that habit.

Design is everything a user touches.

Copy, error messages, empty states, loading sequences — if a user reads it, clicks it, or waits for it, it's design. The hero headline change is a design decision, not a marketing one. At a small team like Unkey, a design engineer who only touches visuals is leaving half the experience unowned.

I care about how it feels and works, not just how it looks.

The interaction layer is part of the design. The toggle glow, the code preview updating as you type, the disabled cursor pointing you to the right control. These aren't polish added at the end. They're design decisions made during the build. How something responds to you matters as much as how it looks.

Product Observations

Spending time inside the Create Key flow gave me a close look at how keys get configured — through the dashboard and through the SDK. A few things stood out that felt like product gaps rather than design ones.

API Key Templates

Every key creation requires the full configuration to be specified from scratch: prefix, credits, rate limits, permissions, expiration. For a SaaS running multiple pricing tiers, this means repeating the same config in every createKey call, or building a template layer on top of Unkey yourself.

I know this isn't just a design change. It touches the data model, the API (templateId as a parameter in createKey), and the dashboard. But I think it's worth raising. The value is concrete: consistency across keys, simpler plan management when a user upgrades, and a reduced API surface for common patterns.

Before

await unkey.keys.create({
  apiId: "api_xxx",
  name: "alice",
  externalId: "user_123",
  prefix: "sk_live",
  byteLength: 16,
  ratelimit: {
    type: "fast",
    limit: 100,
    refillRate: 100,
    refillInterval: 60000,
  },
  remaining: 1000,
  refill: {
    interval: "monthly",
    amount: 1000,
  },
  permissions: ["read", "write"],
  expires: Date.now() + 30 * 24 * 60 * 60 * 1000,
});

After

await unkey.keys.create({
  apiId: "api_xxx",
  templateId: "tmpl_pro_tier",
  name: "alice",
  externalId: "user_123",
});

A pragmatic starting point before committing to full templates: extend the existing keyspace defaults — currently just prefix and byte length — to cover credits, rate limits, and permissions. No new entities, no API changes. Just make the Settings page more complete and have the API respect those defaults when fields are omitted. Proves the concept with minimal investment.