Read the CSS: How to Audit Any Live Site for AI Tells in Your Browser
A stranger's site, one URL, ninety seconds. The CSS ships in plaintext and DevTools reads it back resolved. Here are the six computed properties that tell you whether the "bespoke design system" came out of a generator.
A stranger's site. One URL, no repo access, no package.json, no git history. Just the rendered page and a browser. You have ninety seconds before you decide whether the agency that built it earned its invoice. The good news: the CSS is right there. It always is. Every site ships its stylesheet to the browser in plaintext, and the browser hands you a tool that reads it back, computed and resolved, property by property. You don't need the source. You need to know which six properties to look at.
This is the hands-on version of the 30-second detection method. That article teaches the eye to recognize the signs from across the room. This one teaches the fingers: exactly where to click, what to type into the DevTools filter box, and what number confirms the hunch. By the end you'll open any live site and read its origin off the computed styles like a tox screen.
Open the box: Inspect, then Computed
Right-click the primary call-to-action button — the filled one in the hero — and choose Inspect. Chrome, Edge, Firefox, Safari, all the same. The Elements panel opens with that or highlighted in the DOM tree.
Now ignore the Styles tab for a second and click the Computed tab next to it. Styles shows you the cascade — every rule that *could* apply, including ones overridden. Computed shows the final resolved value the browser actually painted. For detection, Computed is the truth serum. A site can have bg-blue-500 buried in a class three overrides deep that never wins; Computed tells you what won.
There's a filter box at the top of the Computed panel. This is your grep. You're going to type six things into it.
Fingerprint 1: the accent color
Filter the Computed panel for background-color. Read the value on that primary button.
background-color: rgb(59, 130, 246)That's #3b82f6. Tailwind blue-500. The single most reliable tell in the entire toolkit. If the button instead reads rgb(99, 102, 241) that's #6366f1, Tailwind indigo-500, the second-most-common default. Either one, by itself, is a 60-percent signal.
Now check the secondary accent. Click a badge, a pill, an icon background, or a gradient stop. Filter for background-color or color again:
color: rgb(139, 92, 246)#8b5cf6. Tailwind purple-500. The blue-plus-purple pairing is the closest thing to a smoking gun the web has. I documented why it's a signature in the Tailwind blue-purple piece, but the short version: it's the default in every Vercel demo, every shadcn example, every v0 first draft, so it's the default in the training distribution.
A trick for speed: hover the little color swatch next to the value in DevTools and it shows you the hex directly. No rgb()-to-hex math in your head. But memorize the three big ones so you can pattern-match without hovering:
rgb(59, 130, 246)=#3b82f6= blue-500rgb(139, 92, 246)=#8b5cf6= purple-500rgb(99, 102, 241)=#6366f1= indigo-500
One more shortcut. In the Styles tab, if the color reads var(--primary) or oklch(...), you're likely looking at a shadcn/ui theme — modern shadcn moved to OKLCH variables. Confirm it in the Console:
getComputedStyle(document.documentElement).getPropertyValue('--primary')If that spits back something like oklch(0.205 0 0) or a --background/--foreground/--ring set of variables defined on :root, you're looking at the stock shadcn token layer — a fingerprint in its own right, covered below.
Fingerprint 2: the font stack
Click anywhere on the body text. Filter Computed for font-family. The classic read:
font-family: Inter, ui-sans-serif, system-ui, sans-serifInter, no companion face, no display font for headings. To confirm there's no second face, click an or and check its font-family too. If the headline resolves to the same Inter as the paragraph, the site uses one typeface for everything — which is the actual tell, not Inter itself. Inter is a great typeface (Rasmus Andersson did real work). The slop signal is *Inter for 100% of the text with zero pairing*, which is the argument in the Inter problem.
Watch for two disguises. Next.js with next/font rewrites the family to a hashed name:
font-family: '__Inter_aaf875', '__Inter_Fallback_aaf875'That __Inter_ prefix with a hash suffix is itself a fingerprint — it tells you the site is Next.js using the font optimizer, which narrows the generator field to v0, Lovable, or a Next starter. Geist, Vercel's own font, shows up as __Geist_ or Geist Sans and is a near-certain Vercel/v0 marker — Geist has quietly become the new Inter on that stack.
If the stack reads system-ui, -apple-system, ... with no webfont at all, that's a *point toward human* — somebody made a deliberate performance choice that generators rarely default to.
Fingerprint 3: the gradient hero
Go back to the top of the page. Click the hero's headline or its container. In the Styles tab (not Computed this time, because gradients are easier to read in source), scan for background-image or background. You're looking for:
background-image: linear-gradient(to right, #3b82f6, #8b5cf6);or a text gradient, which is the 2026 default for hero headlines:
background: linear-gradient(to right, rgb(59,130,246), rgb(139,92,246));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;That background-clip: text plus text-fill-color: transparent combo is the "gradient headline" pattern. When the gradient runs blue-to-purple, you've confirmed fingerprints 1 and 3 at once. The direction matters less than the stops, but to right and to bottom right (135deg) are the canonical generator angles.
While you're at the top, check the hero's own background-color. If it reads rgb(9, 9, 11) — that's #09090b, Tailwind zinc-950, the default dark-hero canvas v0 and shadcn reach for first. A near-black hero that resolves to exactly that value is another stock pour, not a brand decision.
Search tip: in the Styles panel use Ctrl/Cmd+F inside DevTools to filter rules, and type gradient. Every gradient rule on the inspected element surfaces instantly.
Fingerprint 4: the rounded-2xl + shadow card
Scroll to the first feature grid or pricing section. Inspect one card. Filter Computed for border-radius:
border-radius: 16px16px is Tailwind rounded-2xl. 12px is rounded-xl, 24px is rounded-3xl. The 2xl/16px value is the overwhelming default for cards in generated layouts. Now, same element, filter for box-shadow:
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)That exact two-layer shadow is Tailwind shadow-lg — and the giveaway is that it's rgba(0,0,0,0.1), pure black at 10% with no hue. A designer tuning a shadow usually tints it toward the surface color; the generator ships flat black. The rounded-2xl + shadow-lg pairing on a white card with padding: 24px (p-6) is the most-printed card in the world right now. Check padding while you're there — 24px or 32px (p-8) completes the set.
One card tells you little. Three identical cards in a row tells you a lot — that's the card-grid problem, and it's worth confirming the grid container too. Inspect the parent and filter for display and gap:
display: grid
grid-template-columns: repeat(3, minmax(0, 1fr))
gap: 24pxrepeat(3, minmax(0, 1fr)) is Tailwind grid-cols-3. Three equal columns, gap-6. The canonical slop grid, resolved in the browser, no source needed.
Fingerprint 5: the class-name soup
Switch from Computed back to Elements and just *read the class attribute* on that card in the DOM. You don't need computed styles for this one — the giveaway is the class string itself:
<div class="rounded-2xl border border-gray-200 bg-white p-6 shadow-lg
transition-all hover:shadow-xl hover:-translate-y-1">A long utility chain ending in hover:shadow-xl hover:-translate-y-1 is Tailwind, and the -translate-y-1 lift-on-hover is a generator reflex. If instead you see semantic names like class="pricing-card pricing-card--featured" or BEM-style names, that's a human or a non-Tailwind framework — point away from slop.
shadcn/ui leaves its own residue here. Look for data-slot, data-state, classes containing inline-flex items-center justify-center whitespace-nowrap, or the cn()-merged chaos of forty utilities on a single button. A run of ... ring-offset-background focus-visible:ring-2 focus-visible:ring-ring ... is shadcn's button, near-verbatim. I covered why that monoculture spread in the shadcn piece.
Fingerprint 6: the network and the headers
Open the Network tab. Reload the page. Sort by Type, look at the CSS and JS requests:
- Requests under
/_next/static/→ Next.js. Combined with__Inter_fonts and blue-500, you've narrowed it to the v0/Lovable/Next family. - A single huge
index-[hash].cssplusindex-[hash].jswith no SSR HTML → a Vite SPA, which is what Bolt.new ships by default. chunk-prefixed files and a_appbundle → older Next or a CRA descendant.
Now the Console. Paste this one-liner to dump every unique color the page actually uses, no clicking required:
[...new Set([...document.querySelectorAll('*')].flatMap(el => {
const s = getComputedStyle(el);
return [s.color, s.backgroundColor, s.borderColor];
}))].filter(c => c && c !== 'rgba(0, 0, 0, 0)').sort()If that array is short — five or six colors, with rgb(59, 130, 246) and rgb(139, 92, 246) sitting in it — the site has the flat, tiny, untuned palette of a generator. A human brand system usually shows a fuller ramp with off-true neutrals (rgb(38, 38, 38) instead of pure rgb(0, 0, 0)), tinted shadows, and a non-Tailwind accent that doesn't map to any default.
For fonts, the matching one-liner:
[...new Set([...document.querySelectorAll('*')]
.map(el => getComputedStyle(el).fontFamily))]One or two entries, both Inter? Single-face site. Four or five, including a display face and a mono? Somebody made typographic decisions.
Reading the score
You don't tally these like a court verdict. You weight them. Here's the honest hierarchy after auditing hundreds of pages:
| Fingerprint | What you read | Weight | |---|---|---| | rgb(59,130,246) + rgb(139,92,246) together | Computed background-color/color | High | | background-clip: text blue→purple gradient | Styles background | High | | Single-face Inter, all text | Computed font-family | Medium | | rounded-2xl + shadow-lg + grid-cols-3 | Computed border-radius, box-shadow, grid-template-columns | Medium | | shadcn class soup / data-slot | Elements class attribute | Medium | | /_next/static/ + __Inter_ hashed font | Network tab | Low (narrows the tool, not the verdict) |
Two highs plus one medium and you're looking at pure slop shipped as bespoke. The trap is the false positive: a serious B2B fintech that genuinely chose #3b82f6 because the founder likes that exact blue and the brand means trust. Color alone never convicts. It's the *stack* — blue-500 and untuned Inter and the rounded-2xl grid and the four-column footer — that turns a coincidence into a confession. Any one default is a choice a human might make. All of them together is a model returning its first guess for "modern startup landing page," which is the structural argument behind the 23 code tells: the fingerprint isn't any single value, it's the cluster of defaults that no human picks all at once.
A worked example, start to finish
Say you've got a freelancer's portfolio piece. Ninety seconds:
- Inspect the hero button. Computed →
background-color: rgb(59, 130, 246). Blue-500. Flag. - Inspect the headline. Styles →
background-clip: text, gradientrgb(59,130,246)→rgb(139,92,246). Blue-to-purple text gradient. Flag, flag. - Inspect body text. Computed →
font-family: __Inter_e8ce0c. Inter via next/font. Headline? Same Inter. Single face. Flag. - Inspect a feature card. Computed →
border-radius: 16px,box-shadow: 0 10px 15px -3px.... rounded-2xl + shadow-lg. Parent →grid-cols-3,gap: 24px. Flag. - Network tab.
/_next/static/,__Inter_font,__Geist_nowhere. Next.js, almost certainly v0 or Lovable. - Console color dump. Six colors, blue-500 and purple-500 both present, neutrals are pure-ish grays.
Two highs, two mediums, and a tool ID. The "bespoke design system" on the invoice came out of a generator, got a logo swap, and shipped. You read it off the live CSS in under two minutes with nothing but the browser everyone already has open. If you want to know what a non-generic version of that same portfolio looks like, the before-and-after case study runs one through the rebuild.
Train the eye, then stop clicking
Do this on ten sites and the pattern burns in. You'll start spotting #3b82f6 before you even open DevTools, the way a sommelier calls a region off the first sniff. The manual audit is how you *learn* the fingerprints — it builds the perceptual model that makes the fast scan trustworthy.
But once you can do it by hand, doing it by hand on every URL wastes your afternoon. The whole walkthrough above — pull the computed background-color, resolve it against the Tailwind palette, check the font stack for a single untuned face, measure the border-radius/box-shadow/grid cluster, sniff the class names for shadcn residue, read the bundle paths — is exactly what a scanner does in one request. It fetches the page, parses the rendered CSS, maps every color to its nearest Tailwind token, and returns the cluster as a score instead of a hunch. That's the point of Sailop: it automates the six-fingerprint audit you just learned, so you can run it on a hundred competitor pages instead of one, and only open DevTools when the number surprises you. Learn it by hand. Then let the machine read the CSS.
SHIP CODE THAT LOOKS INTENTIONAL
Scan your frontend for AI patterns. Generate a unique design system. Stop shipping the same blue gradient as everyone else.