DEV Community

woai3c
woai3c

Posted on

How to Extract a Website’s Design Style: DOM, Computed Styles, and DESIGN.md

There are two common ways to extract a website's design style. The first is to give page screenshots to a vision model and have the model identify colors, fonts, spacing, and page structure. The second is to enter a website URL, let a browser load the page, and then read the DOM, computed styles, responsive layout, and interaction states.

Screenshots are suitable for analyzing a single view. URL analysis can compare multiple pages, viewports, and states, and can also record where each rule comes from. Using my open-source project Imprint and its public Astro case, this article explains how the second method is implemented.

Imprint is a desktop application that runs locally. Its input is a website URL, and its outputs include DESIGN.md, CSS Variables, and Tailwind v4 @theme. DESIGN.md is a regular Markdown file that Claude Code, Gemini CLI, and other coding agents capable of reading project files can use. This article uses Codex CLI to demonstrate generating the target pages.

Why Extract a Website's Design Style

A screenshot can provide colors, font sizes, margins, and the current layout, but it does not record the relationships among these values.

For example, a screenshot may contain a 16px gap. This value could be part of the site-wide spacing scale, or it could be a local adjustment for a particular card. The same blue could be used for primary buttons, or it could simply be a decorative color in an illustration. Looking only at the current view cannot distinguish between these cases.

Responsive and interaction states are also absent from a static screenshot. To determine whether a three-column desktop layout becomes a single column on mobile, hides some content, or reorders sections, multiple viewports must be compared. Hover, Focus, Disabled, and expanded states must be observed after the page is running.

When a task contains only one static page, a coding agent can use a screenshot to handle the visual style of the current view. Once the task expands to list pages, settings pages, and mobile layouts, however, these colors, spacing values, and component styles still need to remain consistent across multiple pages. A single screenshot does not record these cross-page rules, so a coding agent may add styles separately for each page, ultimately creating stylistic differences between pages.

Raw CSS Still Needs to Be Combined with Rendering Results

Fetching stylesheets directly can provide more data, but that data will include unused rules, third-party component styles, reset styles, and generated class names. CSS declarations must also go through the cascade, inheritance, media queries, and runtime states before they become the styles actually used by the page.

Screenshots, CSS, and the DOM plus computed styles each record different information from the page-rendering process:

Screenshot: the pixel result at a specified viewport and state
CSS: style declarations the page may use
DOM + computed styles: the style results currently applied to elements
Enter fullscreen mode Exit fullscreen mode

When analyzing a website URL, reading CSS alone is not enough. The browser must first run the website, then read the final styles from visible elements while retaining provenance such as the page, viewport, element role, and interaction state.

Practical Demonstration: Astro URL → DESIGN.md → Harbor Deploy

First, consider the public Astro case. The source website is https://astro.build/, and the target is a neutral deployment console called Harbor Deploy.

design-md-agent-workflow-en.gif

This run performed the following steps in order:

  1. Imprint loaded the Astro URL and analyzed the home page, blog page, and agencies page.
  2. The analysis results were exported as DESIGN.md, CSS Variables, and a Tailwind v4 theme.
  3. Codex CLI read the exported design files together with Harbor Deploy's page requirements for routes, tables, filters, and settings.
  4. The task files separately specified that the generated pages must not copy Astro's branding, copy, assets, or page structure.
  5. Codex CLI generated three pages: overview, deployments, and settings.

Astro's marketing page, blog page, and agencies page use a similar dark visual language, but their content structures differ. The target instead uses the structure of a deployment console, including metric cards, a deployment table, filters, settings, and status feedback, while excluding Astro's brand copy, illustrations, and page structure.

The following is a screenshot of the Astro home page that Imprint saved automatically after loading the website:

Enter image description here

This image was used only to record the source. It was not used as an analysis input and was not passed to Codex CLI. The agent received DESIGN.md, style variables, and the product task. By excluding the source screenshot, it is possible to check whether the agent reused the design rules from the document instead of directly copying Astro's page structure.

The generated Harbor Deploy overview page is shown below:

Enter image description here

This case records a fixed runtime environment and acceptance results. Imprint 0.1.0 analyzed 3 pages and completed 6 page captures, covering desktop, tablet, and mobile viewports in 36.8 seconds. Interaction analysis found 36 candidates, 3 of which were safely observed, while the other 33 were skipped. The generated result was checked at 1440 × 900 and 390 × 844, and the browser console had no warnings or errors.

The Desktop analysis shown in the video actually waited 44 seconds, and the finished video compresses the waiting period. The 36.8-second figure comes from another fixed case run, so the video demonstrates the sequence of operations, while the files and manifest.json in the case directory are the provenance record for these results.

What DESIGN.md Stores

After collecting colors and dimensions from the pages, the values still need to be organized into reusable rules. The following excerpt shows two core rules generated for the Astro case:

#### Core Design Rules

- **Typography:** Use ui-sans-serif, system-ui, sans-serif ...
  with the captured sizes 1rem and weights 300, 400.
  _(high confidence · evidence refs: 3 · scope: astro.build/ · desktop;
  astro.build/agencies · desktop; +1 more scope)_

- **Density and rhythm:** Build recurring padding and gaps from
  the most-used observed spacing values: 16px, 32px, 48px, 80px.
  _(high confidence · evidence refs: 40 · scope: astro.build/ · desktop;
  astro.build/agencies · desktop; +1 more scope)_
Enter fullscreen mode Exit fullscreen mode

This does more than list font and spacing values. It also records confidence, the number of sources, and the applicable scope. The complete DESIGN.md divides its content into several categories:

  • Core Design Rules stores rules that have cross-page support and can be used as defaults.
  • Contextual Component Patterns stores the styles of components such as buttons and input fields in specific contexts; they cannot be extended directly into global rules.
  • Local Design Observations stores facts that appear only on a particular page, in a particular section, or at a particular viewport.
  • Unknowns and Coverage Gaps records content that was not observed, could not be matched reliably, or requires additional evidence.
  • Design Evidence Overview summarizes the page, viewport, component, interaction, and screenshot sources.

The Astro case writes 16px / 32px / 48px / 80px into the core spacing rule because these values have enough cross-page support. Border radii did not produce the same kind of global conclusion. The document permits the corresponding radius only on matching buttons or input fields; observing 9999px multiple times does not justify making every card pill-shaped.

Confidence indicates how much source support the current conclusion has. It does not evaluate the quality of the source website or the generated result. The Astro document contains 56 high-confidence, 32 medium-confidence, and 5 low-confidence results. Items such as the 0.844rem font size, 0.4px letter spacing, and 6px border radius are listed separately in a review checklist, allowing users to locate them directly without rechecking the entire document. These labels do not block exports. They only indicate what should be reviewed and also make later manual auditing easier.

This is also the main difference between DESIGN.md and a token list. Tokens tell the agent which values it can use; DESIGN.md also explains where those values should be used and which cases lack sufficient evidence.

From a URL to DESIGN.md

Imprint processes a website URL in the following order:

Website URL
    ↓
Chrome / Edge loads the page
    ↓
Discover and select representative pages
    ↓
Collect evidence from desktop, tablet, and mobile
    ↓
Read the DOM, computed styles, page structure, and safe interaction states
    ↓
Normalize colors, fonts, spacing, border radii, and shadows
    ↓
Generate tokens, component patterns, and provenance records
    ↓
Export DESIGN.md, CSS Variables, and a Tailwind theme
Enter fullscreen mode Exit fullscreen mode

Loading Pages in a Browser

A regular HTTP request can retrieve only the HTML returned by the server. The main body of an SPA may contain only an empty container, web fonts may not yet have loaded, and client-side routes and runtime components have not executed. Reading the HTML at this point does not provide the DOM that users ultimately see, much less the computed styles of its elements.

Imprint uses playwright-core to control an existing Chrome, Edge, or compatible Chromium browser on the computer. playwright-core does not include a browser itself, so if no compatible browser is installed, the analysis stops and displays the reason.

After opening a page, the analyzer waits for the main resources and fonts to load, checks page states such as login walls, CAPTCHAs, and error pages, and freezes animations that would affect screenshots and dimension measurements. Extraction starts only after that. The entry page runs through the viewports selected by the user in sequence:

for (let i = 0; i < viewportNames.length; i++) {
  throwIfAnalysisAborted(analysisSignal)
  const vpName = viewportNames[i]
  const viewport = VIEWPORTS[vpName] || VIEWPORTS.desktop

  const page = i === 0 && initialPage && !initialPage.isClosed() ? initialPage : await runtime.context.newPage()
  await configurePageViewport(page, vpName, viewport)
  const pageResponseStatus = page !== initialPage ? await navigatePage(page, url) : responseStatus

  const preparation = await preparePageForExtraction(page)
  const health = await ensurePageHealth(page, {
    expectedUrl: url,
    responseStatus: pageResponseStatus,
  })
  // Extract styles and page evidence only after the health check passes
}
Enter fullscreen mode Exit fullscreen mode

This code comes from src/core/analyzer/index.ts. The actual implementation also records the reason for failure at each stage, preventing it from producing conclusions that claim full coverage after a viewport fails to load.

Selecting Pages and Viewports

Analyzing only the home page can easily turn brand presentation that is specific to the home page into site-wide rules. Content pages, list pages, and blog pages provide different layout densities and component examples.

Imprint reads links and the Sitemap, then classifies candidate pages according to general URL semantics. For example, /blog/ is usually assigned to the blog category, while documentation and pricing paths correspond to two other categories. Candidate links are deduplicated, and the selection process lowers the priority of repeatedly selecting pages in the same category. There is no dedicated branch for the Astro domain, brand name, CSS classes, or test IDs; the same logic is used for other websites.

The entry page is captured at the selected desktop, tablet, and mobile viewports. Discovered child pages first use the primary viewport, then add a mobile viewport when the page structure shows signals of mobile changes. The Astro case ultimately produced 3 pages and 6 captures, with the number of captures for each page determined by viewport selection and page-structure signals.

A responsive conclusion also requires confirming that sections in two viewports are the same section. If their DOM paths are similar but their semantic roles differ, the analyzer does not compare them. The Astro case therefore retains the responsive-section-identity-mismatch limitation and does not write unconfirmed section ordering into a responsive rule.

Reading the DOM and Computed Styles

The browser's getComputedStyle returns the styles applied to an element in its current state. It has already processed the cascade, inheritance, media queries, and CSS Variables, making it closer to the page's actual result than reading raw declarations.

A page may contain thousands of DOM nodes. Imprint first excludes hidden and zero-sized elements, then records text colors, background colors, fonts, spacing, borders, border radii, shadows, and layout properties. The extractor's visibility check is shown below:

for (const el of elements) {
  const computed = getComputedStyle(el)

  if (computed.display === 'none' || computed.visibility === 'hidden' || computed.opacity === '0') continue

  const rect = el.getBoundingClientRect()
  if (el !== document.documentElement && el !== document.body && (rect.width <= 0 || rect.height <= 0)) continue

  const color = normalizeObservedColor(computed.color)
  const bgColor = normalizeObservedColor(computed.backgroundColor)
  // Continue recording the element role, style values, and provenance
}
Enter fullscreen mode Exit fullscreen mode

The extractor still retains visible layout containers. A <div> with no text may provide the page background, a card border, or section spacing. The analyzer determines element roles using tags, ARIA attributes, link states, and geometric information.

The same numeric value is also counted separately by purpose. 16px could come from a font size, padding, or a grid gap; only sources in the same category enter the same group of candidates. This prevents a common font size from being treated directly as a spacing token.

Deriving Colors, Fonts, and Spacing

Colors Cannot Be Ranked Only by Frequency

Browsers may return colors as HEX, RGB, RGBA, or colors with transparency. Imprint first converts the colors into a stable format, then merges similar values based on RGB distance. The main body of the clustering code is shown below:

function clusterFrequency(
  frequency: ColorFrequency,
  limit = 20,
  threshold = 30,
): Array<{ hex: string; count: number }> {
  const parsed: ColorRGB[] = []
  for (const [colorStr, count] of frequency) {
    const color = parseColor(colorStr)
    if (!color) continue
    color.count = count
    parsed.push(color)
  }
  parsed.sort((a, b) => b.count - a.count || stableColorKey(a).localeCompare(stableColorKey(b)))

  const clusters: ColorRGB[][] = []
  for (const color of parsed) {
    const cluster = clusters.find((candidate) => colorDistance(candidate[0], color) < threshold)
    if (cluster) cluster.push(color)
    else clusters.push([color])
  }
  // Select the most frequently occurring color in each group as its representative value
}
Enter fullscreen mode Exit fullscreen mode

Clustering can only determine which colors are close to one another; it cannot determine how a color is used. The most frequently occurring color is often the page background, not necessarily the primary action color. Imprint also records whether a color appears in text, backgrounds, links, selected states, or primary action elements, and gives more weight to explicit action backgrounds. A text foreground color or state color is not incorrectly named as the primary button color simply because it appears frequently.

Fonts and Spacing Need Measurement Noise Removed First

Computed styles may return decimal values such as 11.9062px. Such a value may come from scaling or layout calculations, while the original design value is 12px. Token building first normalizes values close to half-pixel increments, then sorts them by purpose and occurrence records:

const fontSizeFreq = normalizeLengthFrequency(frequencyForCategory(styles, 'fontSize', styles.fontSizes))
const sortedFontSizes = numericSort(sortByFrequency(fontSizeFreq).map(pxToRem).filter(uniqueFilter()).slice(0, 8))

const spacingFreq = normalizeLengthFrequency(frequencyForCategory(styles, 'spacing', styles.spacings))
const spacings = sortByFrequency(spacingFreq)
  .filter((value) => {
    if (!isScalarLength(value)) return false
    const number = parseFloat(value)
    return !isNaN(number) && number > 0 && number <= 96
  })
  .filter(uniqueFilter())
  .slice(0, 12)
  .sort((a, b) => parseFloat(a) - parseFloat(b))
Enter fullscreen mode Exit fullscreen mode

The same page may be captured at multiple viewports. For example, the home page may have desktop, tablet, and mobile records, while another page may have only a desktop record. Adding them directly would give the colors and spacing on the home page three times the weight in the statistics. style-merge.ts first calculates the usage proportion of each style category within each viewport, then averages the results for the same URL. This way, adding a mobile record only supplements the responsive evidence and does not increase that page's weight in site-wide token ranking.

Component counts follow the same approach. Only one canonical capture per page is selected for counting component instances, with the desktop viewport preferred; other viewports participate only in responsive observations. Otherwise, the same button appearing once each on desktop, tablet, and mobile would be incorrectly counted as three independent instances, and component confidence would also rise with the number of captures.

Observing Responsive Layouts and Interaction States

Responsive analysis generates conclusions from structural changes to the same section at different viewports, focusing on visibility, order, grid column count, size, and border direction.

The Hero on Astro's blog page changes order between desktop and mobile, and its heading size changes from 48px to 24px; the right border of an Aside section becomes a top border. Because these changes have matching section identities and viewport sources, they can be written into local responsive rules. A section that appears at only one viewport cannot be classified directly as hidden by CSS; the page may also have failed to load completely, or the matching may have failed.

Interaction states use two types of sources. The first consists of passive records of :hover / :focus / :active and similar states declared in stylesheets and computed styles. They prove that the page contains the corresponding styles, but not that a user action was executed. The second consists of style changes observed before and after the browser actually performs a safe action.

Hover and Focus can usually be performed safely. Clicking, submitting forms, purchasing, deleting, logging out, and similar actions may change business data and cannot be triggered automatically just to collect styles. The analyzer filters for safe candidates and writes the remaining items into skipped records.

The Astro case had 36 interaction candidates. It safely executed 3 and skipped 33. The number skipped is retained as-is, preventing unexecuted candidates from being recorded as covered states.

Recording Provenance and Uncovered Scope

For each design rule, it is necessary to know at least which page, viewport, element or section it came from, and how many times it occurred. A style with few sources can be retained as a local observation, but it cannot be written as a site-wide default.

When building Design Evidence, Imprint also generates limitation records. The following code handles page, viewport, and interaction coverage:

const limitations: string[] = []
limitations.push(...(input.limitations || []))

if (uniqueUrls.size < input.expectedPageCount) limitations.push('fewer-pages-than-requested')
if (capturedExpectedCombinations < expectedCaptureCount) limitations.push('fewer-page-viewports-than-requested')
if (viewportCoverage.length < 2) limitations.push('single-viewport')
if (pages.some((page) => page.horizontalOverflow)) limitations.push('horizontal-overflow-observed')

const interactionCandidateCount = input.captures.reduce(
  (sum, capture) => sum + capture.snapshot.interactionCandidates.length,
  0,
)
const safelyObservedCount = interactionObservations.filter((observation) => observation.safety === 'safe-active').length
if (interactionCandidateCount > safelyObservedCount) limitations.push('some-safe-interactions-skipped')
Enter fullscreen mode Exit fullscreen mode

These limitations are written directly into DESIGN.md. The Astro result explicitly records horizontal overflow, safe interactions that were not fully observed, and a responsive section-identity mismatch. A coding agent can use this information to narrow the scope of the relevant rules, while users can locate the parts that need review.

Page screenshots are also provenance records. After loading a URL, Imprint automatically captures screenshots so the page and visual result at that time can be checked. It does not support standalone screenshot files as analysis input, nor does it automatically pass screenshots to an external agent.

Giving the Exported Files to a Coding Agent

The exported files serve the following purposes:

File Purpose
DESIGN.md Describes design rules, component patterns, scope, and limits
CSS Variables Reuses specific style values in the target project
Tailwind v4 @theme Integrates the tokens into a Tailwind v4 project

For a multi-page project, DESIGN.md can be placed in the project root, with CSS Variables or the Tailwind theme loaded from the global style entry. The agent reads the design rules first, then determines the page content and components according to the current product's functional requirements.

For example, when the target page needs an input field, the agent can use the input component pattern in the document and obtain the corresponding color, border radius, and spacing from CSS Variables. If the target page has no component of the same type, the pattern does not apply. The product task always determines content and behavior; the design files constrain only visual and interaction implementations supported by source evidence.

The Harbor Deploy case also provides two other files: TASK.md specifies three routes, tables, filters, settings, and responsive behavior; AGENTS.md forbids copying Astro's name, logo, copy, illustrations, images, and page layout, and also forbids remote assets and dependencies. The source screenshots were not included in the agent context.

Codex CLI was chosen in this case only to demonstrate the generation steps. If it is replaced with Claude Code, Gemini CLI, or another coding agent capable of reading project files and modifying code, the input remains the same set of files. Imprint does not include or run an agent, nor is it responsible for generating Harbor Deploy's business functionality.

The other two generated pages are shown below:

Deployments Settings
Enter image description here Enter image description here

How the Code Is Shared

Imprint has three entry points—Desktop, CLI, and local stdio MCP—but only one implementation of analysis and export:

Desktop ─┐
CLI ─────┼── src/core/analyzer + src/core/export
MCP ─────┘
Enter fullscreen mode Exit fullscreen mode

Electron handles only desktop windows, IPC, history, and file exports; it does not maintain a separate analyzer. This ensures that Desktop and the source-built entry points use the same page discovery, style extraction, token building, and DESIGN.md generation logic.

Only Desktop is currently distributed publicly. CLI and MCP already have source-build entry points, but they are not yet formal installation options. The case used the source-built CLI so it could use a fixed command and export all formats; this does not mean that the 0.1.0 installation package includes CLI or MCP.

Differences Among Extraction Methods

Method Input Information Available Limitations
Screenshots and a vision model One or more screenshots Colors, typography, and structure in the current view Lacks the DOM, responsive states, and rule provenance
Reading CSS directly HTML and stylesheets CSS declarations, variables, and media queries Includes unused styles and cannot represent final computed results
Manual design review Pages and human judgment Rules can be evaluated together with business semantics Time-consuming and difficult to repeat
Browser analysis Website URL DOM, computed styles, multiple viewports, interaction states, and provenance Takes longer, and conclusions are limited by page coverage

Browser analysis expands the observable scope and preserves data provenance and uncertainties. Users still need to review which rules are suitable for the target product.

Cost and Boundaries

Analysis time depends on the number of pages, resource loading, network quality, and the number of interaction candidates. Astro's 36.8 seconds applies only to the fixed run on August 27, 2026, and cannot be used as the estimated duration for other websites.

The following limitations should also be considered:

  • The only supported input is a website URL; standalone screenshot files are not supported.
  • The computer must have Chrome, Edge, or a compatible Chromium browser installed.
  • Conclusions cover only pages, viewports, and states that loaded successfully.
  • Login walls, CAPTCHAs, anti-automation mechanisms, and unstable dynamic content may interrupt collection.
  • Interactions whose safety cannot be confirmed are skipped.
  • Rules with few sources, low confidence, or only local occurrences require manual review.
  • Exported content is used to transfer the observed design language; it does not include authorization to copy the source's brand, copy, images, or other protected content.

Downloads and Public Case

Colors and font sizes can be read directly from computed styles. Distinguishing site-wide rules from local styles, recording the source of each conclusion, and retaining pages and states that were not covered require additional processing after browser collection. The browser provides the rendering results, token building organizes the values, and DESIGN.md stores the rules, scope, and limitations.

I implemented this method as Imprint. Version 0.1.0 provides macOS and Windows Desktop downloads. The application analyzes website URLs locally and exports DESIGN.md, CSS Variables, and Tailwind v4 themes. You can test it with a website you know well and focus on checking which rules lack sources or which conclusions are unreliable.

Top comments (0)