Skip to content

Commit ea93a7b

Browse files
committed
border styling
1 parent 48aeac2 commit ea93a7b

24 files changed

Lines changed: 98 additions & 53 deletions

File tree

.claude/rules/sim-styling.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,15 @@ Icons default `size-[14px]`. Equal h/w → `size-*` (`size-[14px]`, `size-4`), n
4848

4949
## Color Tokens
5050

51-
Value text `--text-body`; muted/placeholder/labels `--text-muted`; icons `--text-icon`; borders `--border-1` (fields) / `--border` (dividers); surfaces `--surface-5` (light) / `--surface-4` (dark); active row `--surface-active`; error `--text-error`. No focus rings on chip surfaces.
51+
Value text `--text-body`; muted/placeholder/labels `--text-muted`; icons `--text-icon`; neutral borders and dividers `--border` (`--border-1` and `--border-muted` are legacy aliases resolving to it; `--divider` is retired); surfaces `--surface-5` (light) / `--surface-4` (dark); active row `--surface-active`; error `--text-error`. No focus rings on chip surfaces.
52+
53+
### Line weight
54+
55+
Neutral border geometry comes from `--border-width`: `1px` by default, dropping to `0.5px` under `@media (min-resolution: 2dppx)` so hidpi displays get a true hairline. Tailwind's `border*` and `divide-*` utilities resolve through it, as do `h-px`/`w-px` — the `px` key is overridden on **`spacing`**, not on `width`/`height`, so a hairline and the `-right-px`/`inset-px` offsets that position it stay in agreement.
56+
57+
**Tune line weight on `--border`, never on `--border-width`.** Browsers floor a border to whole device pixels, so on a 2dppx display every value in `(0, 1px)` collapses to the same single-pixel hairline and the next drawable step is a full `1px` — double. Width has exactly one usable position; perceived weight is a color property. Light mode gains weight by darkening `--border`, dark mode by lightening it.
58+
59+
Draw a line with a real `border-*` utility. Never hand-roll one as `shadow-[inset_0_-1px_0_…]` — box-shadow has its own width and cannot follow the token, so such a line silently renders at double weight against every neighbor. Use an explicit numeric width (or a `ring`/`outline`) only when the line is intentionally emphasized, e.g. focus and selection affordances.
5260

5361
## Chip Components (consumer usage)
5462

apps/sim/app/(interfaces)/chat/components/message/components/markdown-renderer.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ const COMPONENTS = {
9191
}
9292

9393
return (
94-
<div className='my-6 overflow-hidden rounded-lg border border-[var(--divider)] text-sm'>
95-
<div className='flex items-center justify-between border-[var(--divider)] border-b bg-[var(--surface-4)] px-4 py-1.5'>
94+
<div className='my-6 overflow-hidden rounded-lg border border-[var(--border)] text-sm'>
95+
<div className='flex items-center justify-between border-[var(--border)] border-b bg-[var(--surface-4)] px-4 py-1.5'>
9696
<span className='font-sans text-[var(--text-tertiary)] text-xs'>
9797
{codeProps.className?.replace('language-', '') || 'code'}
9898
</span>
@@ -115,12 +115,12 @@ const COMPONENTS = {
115115
),
116116

117117
blockquote: ({ children }: React.HTMLAttributes<HTMLQuoteElement>) => (
118-
<blockquote className='my-4 break-words border-[var(--divider)] border-l-2 pl-4 font-sans text-[var(--text-primary)] italic [&>p:first-child]:mt-0 [&>p:last-child]:mb-0 [&>p]:my-2'>
118+
<blockquote className='my-4 break-words border-[var(--border)] border-l-2 pl-4 font-sans text-[var(--text-primary)] italic [&>p:first-child]:mt-0 [&>p:last-child]:mb-0 [&>p]:my-2'>
119119
{children}
120120
</blockquote>
121121
),
122122

123-
hr: () => <hr className='my-8 border-[var(--divider)] border-t' />,
123+
hr: () => <hr className='my-8 border-[var(--border)] border-t' />,
124124

125125
a: ({ href, children, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement>) => (
126126
<LinkWithPreview href={href || '#'} {...props}>
@@ -139,10 +139,10 @@ const COMPONENTS = {
139139
<thead className='bg-[var(--surface-3)] text-left'>{children}</thead>
140140
),
141141
tbody: ({ children }: React.HTMLAttributes<HTMLTableSectionElement>) => (
142-
<tbody className='divide-y divide-[var(--divider)] bg-[var(--surface-2)]'>{children}</tbody>
142+
<tbody className='divide-y divide-[var(--border)] bg-[var(--surface-2)]'>{children}</tbody>
143143
),
144144
tr: ({ children }: React.HTMLAttributes<HTMLTableRowElement>) => (
145-
<tr className='border-[var(--divider)] border-b transition-colors hover:bg-[var(--surface-hover)]'>
145+
<tr className='border-[var(--border)] border-b transition-colors hover:bg-[var(--surface-hover)]'>
146146
{children}
147147
</tr>
148148
),

apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-chat-loop.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ interface HeroChatLoopProps {
5353
* screenshot is invisible. The reply STREAMS in word by word (the way the
5454
* real Mothership streams its responses); once the text completes, the
5555
* "Suggested follow-ups" block (the real special-tags markup: numbered
56-
* `--divider`-ruled rows with a trailing arrow) and the action row land
56+
* `--border`-ruled rows with a trailing arrow) and the action row land
5757
* together; under `prefers-reduced-motion` it appears whole.
5858
* The content column is centered and capped (like the real full-width
5959
* MothershipChat) so it reads right both full-width (stage collapsed) and at
@@ -153,7 +153,7 @@ export function HeroChatLoop({ phase, fading }: HeroChatLoopProps) {
153153
<span
154154
key={title}
155155
className={cn(
156-
'flex items-center gap-2 border-[var(--divider)] px-2 py-2 text-left',
156+
'flex items-center gap-2 border-[var(--border)] px-2 py-2 text-left',
157157
i > 0 && 'border-t'
158158
)}
159159
>

apps/sim/app/(landing)/components/landing-preview/components/landing-preview-logs/landing-preview-logs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ export function LandingPreviewLogs() {
233233
<col style={{ width: '14%' }} />
234234
<col style={{ width: '18%' }} />
235235
</colgroup>
236-
<thead className='shadow-[inset_0_-1px_0_var(--border)]'>
236+
<thead className='border-[var(--border)] border-b'>
237237
<tr>
238238
{COL_HEADERS.map(({ key, label }) => (
239239
<th

apps/sim/app/(landing)/components/landing-preview/components/landing-preview-resource/landing-preview-resource.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export function LandingPreviewResource({
128128
/>
129129
))}
130130
</colgroup>
131-
<thead className='shadow-[inset_0_-1px_0_var(--border)]'>
131+
<thead className='border-[var(--border)] border-b'>
132132
<tr>
133133
{columns.map((col) => (
134134
<th

apps/sim/app/(landing)/files/components/files-hero-loop.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ export function FilesHeroLoop() {
274274
<div
275275
className={cn(
276276
ROW_GRID,
277-
'h-10 flex-shrink-0 items-center shadow-[inset_0_-1px_0_var(--border)]'
277+
'h-10 flex-shrink-0 items-center border-[var(--border)] border-b'
278278
)}
279279
>
280280
{COL_HEADERS.map((header) => (

apps/sim/app/(landing)/knowledge/components/knowledge-hero-loop.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ export function KnowledgeHeroLoop() {
209209
<col style={{ width: 170 }} />
210210
<col style={{ width: 190 }} />
211211
</colgroup>
212-
<thead className='shadow-[inset_0_-1px_0_var(--border)]'>
212+
<thead className='border-[var(--border)] border-b'>
213213
<tr>
214214
{COL_HEADERS.map((header) => (
215215
<th

apps/sim/app/(landing)/logs/components/logs-hero-loop.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ export function LogsHeroLoop() {
330330
<col key={index} style={{ width }} />
331331
))}
332332
</colgroup>
333-
<thead className='shadow-[inset_0_-1px_0_var(--border)]'>
333+
<thead className='border-[var(--border)] border-b'>
334334
<tr>
335335
{COL_HEADERS.map((label) => (
336336
<th

apps/sim/app/_styles/globals.css

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@
2323
--editor-connections-height: 172px; /* EDITOR_CONNECTIONS_HEIGHT.DEFAULT */
2424
--terminal-height: 206px; /* TERMINAL_HEIGHT.DEFAULT */
2525
--output-panel-width: 560px; /* OUTPUT_PANEL_WIDTH.DEFAULT */
26+
/**
27+
* Neutral border and divider thickness. Standard-density displays cannot draw
28+
* a sub-pixel line crisply, so they keep a full pixel; the hidpi override below
29+
* thins it. Consumed by Tailwind's `border*`, `divide-*`, and `h-px`/`w-px`.
30+
*/
31+
--border-width: 1px;
2632
--auth-primary-btn-bg: #ffffff;
2733
--auth-primary-btn-border: #ffffff;
2834
--auth-primary-btn-text: #000000;
@@ -50,6 +56,18 @@
5056
--shadow-card: 0 1px 3px rgba(0, 0, 0, 0.04);
5157
}
5258

59+
/**
60+
* One device pixel on high-density displays. Browsers floor a border to whole
61+
* device pixels, so every value in (0, 1px) collapses to this same hairline and
62+
* the next drawable step is a full 1px — i.e. double. Line weight between those
63+
* two is therefore tuned on `--border`, never here.
64+
*/
65+
@media (min-resolution: 2dppx) {
66+
:root {
67+
--border-width: 0.5px;
68+
}
69+
}
70+
5371
/**
5472
* Electron's `titleBarOverlay` publishes the window controls' geometry as these
5573
* `titlebar-area-*` env vars, and Chromium rescales them under page zoom so the
@@ -312,9 +330,7 @@ html.sidebar-booting .sidebar-shell-inner {
312330
--surface-2: #ffffff; /* blocks, cards, modals - pure white */
313331
--surface-3: #f7f7f7; /* popovers, headers */
314332
--surface-4: #f5f5f5; /* buttons base */
315-
--border: #dedede; /* primary border */
316333
--surface-5: #f3f3f3; /* inputs, form elements */
317-
--border-1: #e0e0e0; /* stronger border */
318334
--surface-6: #e5e5e5; /* popovers, elevated surfaces */
319335
--surface-7: #d9d9d9;
320336
--surface-hover: #f2f2f2; /* hover state */
@@ -336,10 +352,13 @@ html.sidebar-booting .sidebar-shell-inner {
336352
--text-error: #ef4444;
337353

338354
/* Borders / dividers */
339-
--divider: #ededed;
340-
--border-muted: #e4e4e4;
355+
--border: #d8d8d8;
341356
--border-success: #e0e0e0;
342357

358+
/** Legacy neutral-border aliases. New work should use --border. */
359+
--border-1: var(--border);
360+
--border-muted: var(--border);
361+
343362
/* Brand & state */
344363
--brand-agent: #6f3dfa;
345364
--brand-secondary: #33b4ff;
@@ -470,9 +489,7 @@ html.sidebar-booting .sidebar-shell-inner {
470489
--surface-2: #232323;
471490
--surface-3: #242424;
472491
--surface-4: #292929;
473-
--border: #333333;
474492
--surface-5: #363636;
475-
--border-1: #3d3d3d;
476493
--surface-6: #454545;
477494
--surface-7: #505050;
478495
--surface-hover: #262626; /* hover state */
@@ -493,11 +510,14 @@ html.sidebar-booting .sidebar-shell-inner {
493510
--text-muted-inverse: #b3b3b3;
494511
--text-error: #ef4444;
495512

496-
/* --border-strong: #303030; */
497-
--divider: #393939;
498-
--border-muted: #424242;
513+
/* Borders / dividers */
514+
--border: #444444;
499515
--border-success: #575757;
500516

517+
/** Legacy neutral-border aliases. New work should use --border. */
518+
--border-1: var(--border);
519+
--border-muted: var(--border);
520+
501521
/* Brand & state */
502522
--brand-agent: #701ffc;
503523
--brand-secondary: #33b4ff;

apps/sim/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
5656
strategy='lazyOnload'
5757
/>
5858
)}
59-
{/*
59+
{/*
6060
Workspace layout dimensions: set CSS vars before hydration to avoid layout jump.
6161
6262
IMPORTANT: These hardcoded values must stay in sync with stores/constants.ts

0 commit comments

Comments
 (0)