I recently set out to finally create my own website, a thing I have wanted to do for ages.
As a software engineer, I wanted a place where I can showcase the projects I am working on, share the things I have learned, and highlight the tools I enjoy using.
The reason I put this off for years was not because I did not know how to build it. Making a site fast, getting the basics of SEO right, and shipping something functional is all pretty doable.
The real challenge was always making it look good.
Even though I know the basics of web design, creating a unique design from scratch is not really a skill I have. And because I knew that, the motivation was not there. I had this feeling in the back of my mind that whatever I ship will probably look average at best.
Then I started using AI, not for coding, but for design work.
Honestly, it is scary how good it can be at times.
You can browse this site and judge if it is well designed or not, but I can say one thing: it is infinitely better than what I would have been able to create without the help of AI. During this process, I also learned a few lessons that helped me get a much better result than I expected.
This post is just me sharing what worked for me. It is a simple workflow you can steal and adapt.
The loop looks like this:
- Establish a foundation for your design system
- Prompt for a unique design with clear rules and constraints
- Lock it in by turning the best parts back into cohesive system
Step 1: Establish a solid foundation
AI has no memory. Every new chat is basically a fresh start unless you bring the context back in.
If you skip the foundation, every component you generate is designed in isolation. You might still get nice-looking pieces, but the site will not feel consistent. And consistency is a huge part of what makes something feel high-quality.
Here is what I recommend starting with. Nothing fancy, just enough structure that the AI has boundaries.
- Color system
- Typography system
- Layout rules
- Geometry system
- Design intent
Color system
You want three things:
- A neutral palette for surfaces and text
- One primary color for emphasis and actions
- A small set of semantic colors for states like success, warning, error, and info
If you want a quick starting point, Tailwind and Radix both have great palettes you can borrow from.
Here is a more complete example of a small color system. It has your neutral base, then a few semantic states with both background and foreground colors so you can use them for badges, alerts, toasts, and whatever else.
:root {
--color-text-primary: var(--color-neutral-50);
--color-background-primary: var(--color-neutral-950);
--color-primary-background: var(--color-sky-600);
--color-primary-foreground: var(--color-sky-50);
--color-success-background: var(--color-green-600);
--color-success-foreground: var(--color-green-50);
--color-warning-background: var(--color-yellow-600);
--color-warning-foreground: var(--color-yellow-50);
--color-error-background: var(--color-red-600);
--color-error-foreground: var(--color-red-50);
--color-info-background: var(--color-blue-600);
--color-info-foreground: var(--color-blue-50);
} The point is not the exact values. The point is that you have a few consistent pairs you can reuse everywhere.
Typography system
Start simple. Two fonts is enough:
- A sans font for most text
- A mono font for code and small UI bits
Here is a more complete typography setup that is still simple. Two font families, a size scale, and a few weights so you can keep hierarchy consistent.
:root {
--font-family-sans: "Inter Variable", sans-serif;
--font-family-mono: "JetBrains Mono Variable", monospace;
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-md: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--font-weight-light: 300;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
} You can add more fonts later when you have a real reason. Starting with a big font collection usually just creates inconsistency.
Layout rules
Inconsistent layout makes a site feel broken, even if everything else is nice. So decide your basic layout rules early:
- What is the maximum container width?
- Do you use a header, a sidebar, or both?
- What changes on mobile, tablet, and desktop?
Here is a basic layout setup I like. It gives you a container and a few screen sizes so you can keep your layout decisions consistent.
:root {
--screen-mobile: 480px;
--screen-tablet: 768px;
--screen-laptop: 1280px;
--screen-desktop: 1536px;
} You do not need to design every breakpoint up front. Just decide the lines you do not want to cross.
Geometry system
This sounds fancy but it is just a consistency check.
If your avatar is a circle, your icon buttons probably should be circular too. If your cards are softly rounded, your inputs and badges should not be sharp rectangles.
I like having a proper radius and spacing scale so I do not make random choices per component:
:root {
--border-radius-xs: 0.125rem;
--border-radius-sm: 0.25rem;
--border-radius-md: 0.375rem;
--border-radius-lg: 0.5rem;
--border-radius-xl: 0.75rem;
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-5: 1.5rem;
} Design intent
You need a short description that explains what the interface should feel like. Something you can paste into prompts. You can get as artsy here as you would like. Simply describe what it should look and feel like when a user lands on your website.
For example:
Write your own version of that. Make it personal. This is the thing that guides decisions when the AI starts getting creative.
Step 2: Prompt like you mean it
Once the foundation is in place, now you can ask AI to design your home page, a project card, a header, whatever.
One thing I learned quickly: most AI outputs look generic by default. It tends to pick the same safe fonts, the same predictable layouts, and the same overdone effects.
So you have to teach it not to generate slop.
If you are not a designer, you probably also lack the vocabulary to describe what you want. That is normal. The easiest way to level up is to read a good design prompt and steal the structure.
This Claude Code skill is a great example of what a strong design direction looks like:
It basically forces the AI to pick a clear direction and commit to it. That part is important. You do not want polite average design. You want a real point of view, then you can dial it back later.
Here is the kind of direction that helped me get better results. The idea is to push the AI away from safe default choices and into something more opinionated.
Example directions I would give:
The first result will probably not match your taste perfectly. That is fine. The goal of the first pass is to get something you can react to.
Then you iterate:
- Keep what you like
- Call out what feels wrong
- Repeat with tighter constraints
Step 3: Lock it in
When you finally get a design that you like, do not just copy-paste it and move on. Read the code and ask yourself:
What parts of this are reusable
You will usually find patterns like:
- A specific shadow style that looks good across cards
- A motion curve that feels nicer than the default ease
- A spacing rhythm that makes layouts breathe
- A background texture or subtle effect that gives the whole site character
In my case, a lot of that ended up as tokens in app.css. Things like shadows, transition durations, easing curves, and z-index layers.
Here is a more complete example of the kind of tokens I mean. Shadows, motion, and layering are the type of stuff you can lock in once and reuse everywhere.
:root {
--shadow-xs: 0 1px 2px oklch(0% 0 0 / 0.15);
--shadow-sm: 0 2px 4px oklch(0% 0 0 / 0.2);
--shadow-md: 0 4px 8px oklch(0% 0 0 / 0.25);
--shadow-lg: 0 8px 16px oklch(0% 0 0 / 0.3);
--shadow-xl: 0 16px 32px oklch(0% 0 0 / 0.35);
--duration-faster: 100ms;
--duration-fast: 150ms;
--duration-normal: 200ms;
--duration-slow: 250ms;
--duration-slower: 300ms;
--ease-linear: linear;
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
--z-base: 0;
--z-dropdown: 100;
--z-sticky: 200;
--z-overlay: 300;
--z-modal: 400;
--z-popover: 500;
--z-tooltip: 600;
--z-toast: 700;
--color-overlay: oklch(0% 0 0 / 0.5);
} This is what I mean by locking it in. You let AI help you discover the look for your site, and then you promote it into your foundation so every future component benefits.
Over time, the amount of custom code you need from AI drops because your base system gets stronger. The AI becomes less of a random generator and more of a partner that suggests variations inside your style.
Conclusion
AI is beyond capable, but you need to learn how to use it for your benefit.
For design, what worked for me came down to three things:
- Establish a solid foundation for your new design
- Prompt like your life depends on it to create a unique look
- Lock it in by moving the reusable parts back into your design system
If you do that, you get the best of both worlds. You keep a cohesive look, but you also get the creative boost that is hard to produce on your own.
It will take some practice but the results are incredible once you get the hang of it.