Build Studio has no per-device styles. Everything it saves applies at every width:
- The Props Editor's Style tab writes the element's inline
styleattribute: one value for every width. - The Monitor and Smartphone buttons at the top of the Style tab do nothing. They only log to the console (
property-style-media-switch.tsx9–11). - The Desktop, Tablet and Mobile buttons in the top bar only resize the canvas (step 4).
- The page record's Breakpoints list (
data.breakpoints, sm / md / lg) isn't read by the site.
A page adapts to width only through CSS that you write: media queries, or Tailwind responsive classes (md:, lg:). Both work on the hosted site (tested at 390, 768 and 1440 px). Variants and data-show can't help: the server always sees "desktop", and the page runtime has no device key (see Show different content to different visitors).
For the page record, see Build a website page. For root and its shared CSS, see Shared header and footer. For motion, see Animate elements on a page.
Ask first
- Which page, or the whole site? One page's rules go in its head; rules for the header, footer and every page go in root's Page settings → Style.
- What is wrong, and at what width? "Cut off on my iPhone", "three columns are too narrow on a tablet". If they don't know, check at 390, 768 and 1440 first (step 5) and show them.
- What should change on a phone? Stack or hide, smaller type, a different image, a burger menu. Use their words. Don't drop content they didn't ask to hide.
- Is the page live? On a site that serves drafts, a save is live at once.
Steps
1. Build Studio: read the page and what wraps it
Read the page with repo_find_one (the find result is partial) and the site's root page the same way. Note:
- The active screen's
html: its<head><style>, and every inlinestyle="…"that sets a size. Search forstyle="withwidth,min-width,height,padding,margin,font-sizeorleft/right. Each one is the same at every width, and a media query can't change it (step 2). - root's
style.css: the site's shared CSS. Note its media queries and its breakpoints, and use the same ones on the page. - Animated elements (
config.animationson the screen): poses that hold pixel widths are written inline by the site and break a phone layout. See Animate elements on a page step 2.
Hands on: the page id, the inline sizes to move into classes, and the site's breakpoints. Check: you can name each element that sets a fixed size.
2. Build Studio: the page's phone and tablet rules
Put the rules in a <style> in the screen's <head> (Build Studio keeps the head as written; edit it in code view). page_write with css (Page settings → Style) works too, and appends.
How the CSS reaches the page. The site writes one stylesheet holding root's Page settings CSS, then a parent page's, then the page's, then the site's own site.style.css (base-app page-assets.ts 30–71). The page's <head> styles come after it. Tested order on a page: the Tailwind build first, then root's CSS, then the page's head. At equal specificity the later rule wins, so a page's rule beats root's.
Write classes, not inline styles. An inline style beats any media query that doesn't use !important. Tested at 390 px: style="padding:80px" stayed 80px against @media (max-width:760px){ .x{ padding:10px } }; with !important it became 10px. Move the sizes from step 1 into a class and delete them from style.
The pattern, desktop first, with the breakpoints PhaZZer's site uses (its assets/site.css):
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
.phone-only { display: none; }
.h1 { font-size: clamp(40px, 5.6vw, 96px); } /* fluid type: no breakpoint needed */
img { max-width: 100%; height: auto; }
@media (max-width: 1100px) { /* tablet */
.cards { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 760px) { /* phone */
.cards { grid-template-columns: 1fr; }
.desktop-only { display: none; }
.phone-only { display: block; }
.h1 { font-size: clamp(30px, 8.6vw, 40px); }
input, select, textarea { font-size: 16px; } /* iOS zooms into smaller fields */
}Tested at 390 / 768 / 1440 px: the grid went 1 / 2 / 3 columns, and the desktop-only and phone-only blocks swapped.
Or Tailwind classes. Every site page loads the Tailwind v4 browser build (@tailwindcss/browser@4, base-app page-assets.ts 194), so Tailwind classes work on the hosted page, including responsive ones. Tested: hidden md:block was hidden at 390 and shown at 768; grid grid-cols-1 md:grid-cols-3 gave 1 then 3 columns; px-6 lg:px-12 gave 24px then 48px.
| Prefix | Applies from |
|---|---|
| (none) | every width |
sm: | 640 px |
md: | 768 px (tested) |
lg: | 1024 px (tested) |
xl: | 1280 px |
2xl: | 1536 px |
Tailwind prefixes mean "this width and up", so write the phone style plain and add the larger ones with prefixes: class="text-2xl md:text-4xl".
A class base-app doesn't use itself is built by that script after the page loads (about 0.3 s on the local site), so it can flash at first. Colour names like bg-primary and text-secondary are Build Studio's sample palette (primary is #0cd6a0, tested), not the site's brand colours. Use the site's own colours.
Hiding per device: display: none inside a media query, or hidden md:block (phone hidden) and md:hidden (phone only). The hidden part is still in the page's HTML and still downloads its images.
Components with their own breakpoints:
| Component | Responsive setting |
|---|---|
wm-grid | columns (below 640 px, default 1), then columns-sm, columns-md, columns-lg, columns-xl (1–6, defaults 2 / 3 / 4 / 4) from 640 / 768 / 1024 / 1280 px |
wm-mega-menu | mobile-breakpoint (default 768): below it, a burger and a full-screen menu |
wm-slider | slides-per-view is one value for every width. To show fewer on a phone, set the attribute from page JavaScript with matchMedia; the slider rebuilds when it changes |
All attributes are in Appmint components on a page.
Hands on: the rules in the screen's head (or classes on the elements).
Check: no element the rules target still carries the same property in its inline style.
3. Page settings → Style on root: site-wide rules and resets
Rules for the header, footer and every page go in root's Page settings → Style → CSS (style.css on the root record; page_write on root's id with css). That stylesheet also loads on the built-in login, account and checkout pages (Shared header and footer step 2).
Tailwind's layers decide who wins. The platform's own stylesheet is Tailwind v4: its reset (preflight) sits in @layer base, and the utility classes that style the cart drawer, checkout, account and login screens sit in @layer utilities (tested layer order: theme, base, components, utilities). CSS outside any layer beats every layered rule, whatever its specificity. So:
- Element resets go in
@layer base. An unlayered*{margin:0;padding:0},button{background:none;border:0}ora{color:inherit}overrides the platform's utilities and strips padding, borders and button colours from those screens. Tested: an unlayered.x p{padding:0}turned ap-8paragraph's 32px padding into 0, while the same rule inside@layer baseleft it at 32px. - Your own classes stay unlayered. They then beat Tailwind on your pages, which is what you want.
/* root → Page settings → Style → CSS */
@layer base {
* { box-sizing: border-box; margin: 0; padding: 0; }
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
}
body { background: #0a0b0f; color: #eeeae2; font: 16px/1.6 Inter, system-ui, sans-serif; }
/* …the site's own classes and media queries, unlayered… */This is how PhaZZer's site is built (assets/site.css, uploaded as root's CSS).
What the platform's CSS does to a page that doesn't set things itself:
| Platform rule | Effect | Do |
|---|---|---|
Tailwind preflight (@layer base) | Headings have no size or weight of their own (tested: a plain <h2> was 16px); lists have no bullets; images are display: block | Size headings and style lists yourself |
base-app styles/style.css (unlayered) | body { font-family: Arial } | Set the font on body |
Dark mode (app/layout.tsx 90–104) | A visitor whose device is in dark mode gets class="dark" on <html> (tested), so dark: classes apply (tested), and the platform's body background in @layer base turns near-black | Always set body's background and color yourself |
| Viewport | The site already sends <meta name="viewport" content="width=device-width, initial-scale=1"> | Keeping your own in the head is harmless |
Stop: root's CSS changes every page of the site, and the login, account and checkout pages. Show the person what changes and where, and wait for yes.
Hands on: nothing new.
Check: repo_find_one on root shows style.css at the top level, with element resets inside @layer base { … }.
4. Build Studio: preview widths (a first look only)
| Button | Canvas width |
|---|---|
| Desktop | the full canvas |
| Tablet | the full canvas too: on a web page it changes nothing (app.tsx 226–230) |
| Mobile | 448 px (max-w-md) |
| Preview (projector), then Tablet or Mobile | 768 × 1024 or 385 × 874; Orientation swaps them (iframe-preview.tsx 83–93) |
| External preview | /__preview/page/<site>/<page> on the site: the real page, in a new tab |
Media queries in the head apply at the canvas width, so Mobile and Preview show phone rules. The canvas is still not the site: check the real URL in step 5.
5. Check it on the site at real widths
Open https://<site host>/<slug> in headless Chrome (CDP) or Chrome DevTools' device toolbar, with mobile emulation (Emulation.setDeviceMetricsOverride with mobile: true), at 390 × 844, 768 × 1024 and 1440 × 1000. Wait until the page has finished starting up. At each width:
window.innerWidthequals the width you set. If it is larger, something is wider than the screen and the phone zooms the whole page out. Tested: a 1200px-wide block at 390 madeinnerWidth1224, and the header's cart and menu buttons ended up off the right edge.overflow-x: hiddenonbodydoesn't prevent this. Find the culprit:[...document.querySelectorAll('body *')].filter(e => e.getBoundingClientRect().right > 391).map(e => e.outerHTML.slice(0, 80))The header's menu and cart are visible; the burger shows on phones.
Each grid has the columns you meant (
getComputedStyle(el).gridTemplateColumns); each phone-only or desktop-only part shows or hides.Text is readable and no button is cut off. Take a screenshot at each width and look at it.
Open
/loginor/checkoutonce if root's CSS changed: their buttons and fields still have padding and colour.
When it's done
Tell the person: what changes at each width (390, 768, 1440) and what is hidden where, where the rules live (the page head, root's CSS, or classes), any inline sizes you moved into classes, and any element resets moved into @layer base. Show the three screenshots.
What it can't do yet
- No per-device styles in Build Studio. The Style tab is inline and applies at every width; its Desktop/Mobile switch does nothing.
- The Tablet canvas button doesn't narrow the canvas on a web page; use Preview.
- The page's Breakpoints setting isn't used by the site.
- AI › Improve Layout applies only the HTML the AI returns; any CSS it returns, media queries included, is dropped (
AIContextMenu.tsx913–950). - A different page per device, or a variant by device, isn't possible: the server can't tell a phone from a desktop.
wm-sliderhas oneslides-per-viewfor every width.
Don'ts
- Don't set sizes in the Style tab, and don't leave widths in inline
style; media queries can't override them. - Don't put unlayered element resets in root's CSS; wrap them in
@layer base. - Don't use Tailwind colour names for the brand.
- Don't trust the Build Studio canvas; check the site at 390, 768 and 1440.
- Don't hide content on phones that the person didn't ask to hide.
- Don't promise a mobile-only variant or a separate mobile page.