@djangocfg/ui-core 2.1.634 → 2.1.635

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-core",
3
- "version": "2.1.634",
3
+ "version": "2.1.635",
4
4
  "description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
5
5
  "keywords": [
6
6
  "ui-components",
@@ -119,7 +119,8 @@
119
119
  "./styles/presets/windows": "./src/styles/css/presets/windows.css",
120
120
  "./styles/presets/soft": "./src/styles/css/presets/soft.css",
121
121
  "./styles/presets/dense": "./src/styles/css/presets/dense.css",
122
- "./styles/presets/high-contrast": "./src/styles/css/presets/high-contrast.css"
122
+ "./styles/presets/high-contrast": "./src/styles/css/presets/high-contrast.css",
123
+ "./styles/presets/notion": "./src/styles/css/presets/notion.css"
123
124
  },
124
125
  "files": [
125
126
  "dist",
@@ -138,8 +139,8 @@
138
139
  "check:docs": "node scripts/check-docs.mjs"
139
140
  },
140
141
  "peerDependencies": {
141
- "@djangocfg/api": "^2.1.634",
142
- "@djangocfg/i18n": "^2.1.634",
142
+ "@djangocfg/api": "^2.1.635",
143
+ "@djangocfg/i18n": "^2.1.635",
143
144
  "consola": "^3.4.2",
144
145
  "lucide-react": "^1.68.0",
145
146
  "moment": "^2.30.1",
@@ -214,10 +215,10 @@
214
215
  "vaul": "1.1.2"
215
216
  },
216
217
  "devDependencies": {
217
- "@djangocfg/api": "^2.1.634",
218
- "@djangocfg/eslint-config": "^2.1.634",
219
- "@djangocfg/i18n": "^2.1.634",
220
- "@djangocfg/typescript-config": "^2.1.634",
218
+ "@djangocfg/api": "^2.1.635",
219
+ "@djangocfg/eslint-config": "^2.1.635",
220
+ "@djangocfg/i18n": "^2.1.635",
221
+ "@djangocfg/typescript-config": "^2.1.635",
221
222
  "@storybook/react-vite": "^10.6.5",
222
223
  "@types/node": "^24.13.3",
223
224
  "@types/react": "19.3.0",
@@ -4,7 +4,7 @@ import * as React from 'react';
4
4
  import { cn } from '../../../lib/utils';
5
5
 
6
6
  const badgeVariants = cva(
7
- "inline-flex items-center rounded-[var(--radius)] border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-1 focus:ring-ring",
7
+ "inline-flex items-center rounded-(--badge-radius) border px-(--badge-px) py-(--badge-py) text-(length:--badge-font-size) font-(weight:--badge-weight) transition-colors focus:outline-none focus:ring-1 focus:ring-ring",
8
8
  {
9
9
  variants: {
10
10
  variant: {
@@ -15,6 +15,15 @@ const badgeVariants = cva(
15
15
  destructive:
16
16
  "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
17
17
  outline: "text-foreground",
18
+ gray: "border-transparent bg-(--tag-gray-bg) text-(--tag-gray-fg)",
19
+ brown: "border-transparent bg-(--tag-brown-bg) text-(--tag-brown-fg)",
20
+ orange: "border-transparent bg-(--tag-orange-bg) text-(--tag-orange-fg)",
21
+ yellow: "border-transparent bg-(--tag-yellow-bg) text-(--tag-yellow-fg)",
22
+ green: "border-transparent bg-(--tag-green-bg) text-(--tag-green-fg)",
23
+ blue: "border-transparent bg-(--tag-blue-bg) text-(--tag-blue-fg)",
24
+ purple: "border-transparent bg-(--tag-purple-bg) text-(--tag-purple-fg)",
25
+ pink: "border-transparent bg-(--tag-pink-bg) text-(--tag-pink-fg)",
26
+ red: "border-transparent bg-(--tag-red-bg) text-(--tag-red-fg)",
18
27
  },
19
28
  },
20
29
  defaultVariants: {
@@ -14,15 +14,25 @@ const Table = React.forwardRef<
14
14
  * double border and a radius that doesn't match the parent's.
15
15
  */
16
16
  frame?: boolean
17
+ /**
18
+ * A database grid, as in Notion: no header band, hairline column dividers, 36px rows,
19
+ * regular-weight column labels. Pair with `frame={false}` on a page-level table.
20
+ */
21
+ grid?: boolean
17
22
  }
18
- >(({ className, containerClassName, frame = true, ...props }, ref) => {
23
+ >(({ className, containerClassName, frame = true, grid = false, ...props }, ref) => {
19
24
  // Scroll container. Cap its height via `containerClassName`
20
25
  // (e.g. "max-h-80") to get a scrollable body with a sticky header.
21
26
  const scroll = (
22
27
  <div className={cn("w-full overflow-auto", containerClassName)}>
23
28
  <table
24
29
  ref={ref}
25
- className={cn("w-full caption-bottom text-sm", className)}
30
+ className={cn(
31
+ "w-full caption-bottom text-sm",
32
+ grid &&
33
+ "[&_thead]:bg-transparent [&_th]:h-8 [&_th]:border-r [&_th]:border-divider [&_th]:font-normal [&_td]:h-9 [&_td]:border-r [&_td]:border-divider [&_td]:py-0 [&_th:last-child]:border-r-0 [&_td:last-child]:border-r-0",
34
+ className
35
+ )}
26
36
  {...props}
27
37
  />
28
38
  </div>
@@ -11,7 +11,7 @@ import { Link } from '../../navigation/link';
11
11
  import { buttonSizeClass, CONTROL_SIZE } from '../control-size';
12
12
 
13
13
  const buttonVariants = cva(
14
- "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-[var(--radius)] text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
14
+ "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-[var(--radius)] text-sm font-(weight:--button-weight) transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
15
15
  {
16
16
  variants: {
17
17
  variant: {
@@ -40,7 +40,7 @@ const buttonVariants = cva(
40
40
  xs: cn(buttonSizeClass('xs'), "rounded-[var(--radius-sm)]"),
41
41
  lg: cn(buttonSizeClass('lg'), "rounded-[var(--radius)]"),
42
42
  huge: cn(buttonSizeClass('huge'), "rounded-[var(--radius)]"),
43
- icon: cn(CONTROL_SIZE.default.height, "w-9"),
43
+ icon: cn(CONTROL_SIZE.default.height, "w-(--control-h-default)"),
44
44
  },
45
45
  // Most variants ship a baked-in `shadow`/`shadow-sm` for a lifted look.
46
46
  // `elevated={false}` flattens that (e.g. controls grouped inside a card —
@@ -22,32 +22,32 @@ import { cn } from '../../lib/utils';
22
22
  */
23
23
  export const CONTROL_SIZE = {
24
24
  xs: {
25
- height: 'h-7',
26
- minHeight: 'min-h-7',
25
+ height: 'h-(--control-h-xs)',
26
+ minHeight: 'min-h-(--control-h-xs)',
27
27
  button: 'px-2 py-1 text-xs',
28
28
  field: 'px-2 py-0.5 text-xs',
29
29
  },
30
30
  sm: {
31
- height: 'h-8',
32
- minHeight: 'min-h-8',
31
+ height: 'h-(--control-h-sm)',
32
+ minHeight: 'min-h-(--control-h-sm)',
33
33
  button: 'px-3 text-sm',
34
34
  field: 'px-2.5 py-1 text-sm',
35
35
  },
36
36
  default: {
37
- height: 'h-9',
38
- minHeight: 'min-h-9',
37
+ height: 'h-(--control-h-default)',
38
+ minHeight: 'min-h-(--control-h-default)',
39
39
  button: 'px-4 py-2',
40
40
  field: 'px-3 py-1 text-base md:text-sm',
41
41
  },
42
42
  lg: {
43
- height: 'h-11',
44
- minHeight: 'min-h-11',
43
+ height: 'h-(--control-h-lg)',
44
+ minHeight: 'min-h-(--control-h-lg)',
45
45
  button: 'px-8',
46
46
  field: 'px-4 py-2 text-base',
47
47
  },
48
48
  huge: {
49
- height: 'h-14',
50
- minHeight: 'min-h-14',
49
+ height: 'h-(--control-h-huge)',
50
+ minHeight: 'min-h-(--control-h-huge)',
51
51
  button: 'px-10 text-lg',
52
52
  },
53
53
  } as const;
@@ -28,7 +28,7 @@ const ContextMenuSubTrigger = React.forwardRef<
28
28
  <ContextMenuPrimitive.SubTrigger
29
29
  ref={ref}
30
30
  className={cn(
31
- "flex cursor-default select-none items-center rounded-[var(--radius-sm)] px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
31
+ "flex cursor-default select-none items-center rounded-[var(--radius-sm)] px-(--menu-item-px) py-(--menu-item-py) text-(length:--menu-font-size) outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
32
32
  inset && "pl-8",
33
33
  className
34
34
  )}
@@ -47,7 +47,7 @@ const ContextMenuSubContent = React.forwardRef<
47
47
  <ContextMenuPrimitive.SubContent
48
48
  ref={ref}
49
49
  className={cn(
50
- "z-[1400] min-w-32 overflow-hidden rounded-[var(--radius-popover)] border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:duration-75 data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-98 motion-reduce:data-[state=open]:animate-none",
50
+ "z-[1400] min-w-32 overflow-hidden rounded-[var(--radius-popover)] border bg-popover p-(--menu-padding) text-popover-foreground shadow-(--popup-shadow) data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:duration-75 data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-98 motion-reduce:data-[state=open]:animate-none",
51
51
  className
52
52
  )}
53
53
  {...props}
@@ -63,7 +63,7 @@ const ContextMenuContent = React.forwardRef<
63
63
  <ContextMenuPrimitive.Content
64
64
  ref={ref}
65
65
  className={cn(
66
- "z-[1400] max-h-(--radix-context-menu-content-available-height) min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-[var(--radius-popover)] border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:duration-75 data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-98 motion-reduce:data-[state=open]:animate-none",
66
+ "z-[1400] max-h-(--radix-context-menu-content-available-height) min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-[var(--radius-popover)] border bg-popover p-(--menu-padding) text-popover-foreground shadow-(--popup-shadow) data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:duration-75 data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-98 motion-reduce:data-[state=open]:animate-none",
67
67
  className
68
68
  )}
69
69
  {...props}
@@ -85,7 +85,7 @@ const ContextMenuItem = React.forwardRef<
85
85
  ref={ref}
86
86
  data-variant={variant}
87
87
  className={cn(
88
- "relative flex cursor-default select-none items-center gap-2 rounded-[var(--radius-sm)] px-2 py-1 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
88
+ "relative flex cursor-default select-none items-center gap-2 rounded-[var(--radius-sm)] px-(--menu-item-px) py-(--menu-item-py) text-(length:--menu-font-size) outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
89
89
  "[&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground",
90
90
  "data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive [&[data-variant=destructive]_svg]:text-destructive",
91
91
  inset && "pl-8",
@@ -103,7 +103,7 @@ const ContextMenuCheckboxItem = React.forwardRef<
103
103
  <ContextMenuPrimitive.CheckboxItem
104
104
  ref={ref}
105
105
  className={cn(
106
- "relative flex cursor-default select-none items-center gap-2 rounded-[var(--radius-sm)] py-1 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
106
+ "relative flex cursor-default select-none items-center gap-2 rounded-[var(--radius-sm)] py-(--menu-item-py) pl-8 pr-2 text-(length:--menu-font-size) outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
107
107
  "[&_svg]:size-4 [&_svg]:shrink-0",
108
108
  className
109
109
  )}
@@ -128,7 +128,7 @@ const ContextMenuRadioItem = React.forwardRef<
128
128
  <ContextMenuPrimitive.RadioItem
129
129
  ref={ref}
130
130
  className={cn(
131
- "relative flex cursor-default select-none items-center gap-2 rounded-[var(--radius-sm)] py-1 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
131
+ "relative flex cursor-default select-none items-center gap-2 rounded-[var(--radius-sm)] py-(--menu-item-py) pl-8 pr-2 text-(length:--menu-font-size) outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
132
132
  "[&_svg]:size-4 [&_svg]:shrink-0",
133
133
  className
134
134
  )}
@@ -153,7 +153,7 @@ const ContextMenuLabel = React.forwardRef<
153
153
  <ContextMenuPrimitive.Label
154
154
  ref={ref}
155
155
  className={cn(
156
- "px-2 py-1.5 text-sm font-semibold text-foreground",
156
+ "px-(--menu-item-px) py-(--menu-item-py) text-(length:--menu-label-font-size) font-(weight:--menu-label-weight) text-(color:--menu-label-color) text-foreground",
157
157
  inset && "pl-8",
158
158
  className
159
159
  )}
@@ -46,7 +46,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
46
46
  <DropdownMenuPrimitive.SubTrigger
47
47
  ref={ref}
48
48
  className={cn(
49
- "flex cursor-default select-none items-center gap-2 rounded-[var(--radius-sm)] px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
49
+ "flex cursor-default select-none items-center gap-2 rounded-[var(--radius-sm)] px-(--menu-item-px) py-(--menu-item-py) text-(length:--menu-font-size) outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
50
50
  inset && "pl-8",
51
51
  className
52
52
  )}
@@ -71,7 +71,7 @@ const DropdownMenuSubContent = React.forwardRef<
71
71
  // and clip. `min()` of the space Radix measured to the viewport edge and a
72
72
  // comfortable ceiling — a menu that spans the WHOLE screen reads as a page,
73
73
  // not a menu, so we stop well short of the viewport even when it would fit.
74
- "z-[1400] max-h-[min(var(--radix-dropdown-menu-content-available-height),24rem)] min-w-32 overflow-y-auto overflow-x-hidden rounded-[var(--radius-popover)] border bg-popover backdrop-blur-xl p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
74
+ "z-[1400] max-h-[min(var(--radix-dropdown-menu-content-available-height),24rem)] min-w-32 overflow-y-auto overflow-x-hidden rounded-[var(--radius-popover)] border bg-popover backdrop-blur-xl p-(--menu-padding) text-popover-foreground shadow-(--popup-shadow) data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
75
75
  className
76
76
  )}
77
77
  {...props}
@@ -106,7 +106,7 @@ const DropdownMenuContent = React.forwardRef<
106
106
  // render behind it (reported: language list clipped under the dialog).
107
107
  // So the layer order is: sheet(200) < shell(1000/1001) < dialog(1300) <
108
108
  // popover(1400). A popover always belongs on top of its opener.
109
- "z-[1400] max-h-[min(var(--radix-dropdown-menu-content-available-height),24rem)] min-w-32 overflow-y-auto overflow-x-hidden rounded-[var(--radius-popover)] border bg-popover backdrop-blur-xl p-1 text-popover-foreground shadow-md",
109
+ "z-[1400] max-h-[min(var(--radix-dropdown-menu-content-available-height),24rem)] min-w-32 overflow-y-auto overflow-x-hidden rounded-[var(--radius-popover)] border bg-popover backdrop-blur-xl p-(--menu-padding) text-popover-foreground shadow-(--popup-shadow)",
110
110
  "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
111
111
  className
112
112
  )}
@@ -127,7 +127,7 @@ const DropdownMenuItem = React.forwardRef<
127
127
  }
128
128
  >(({ className, inset, href, variant = "default", children, ...props }, ref) => {
129
129
  const classes = cn(
130
- "relative flex cursor-default select-none items-center gap-2 rounded-[var(--radius-sm)] px-2.5 py-1 text-sm outline-none transition-colors focus:bg-foreground/[0.07] data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
130
+ "relative flex cursor-default select-none items-center gap-2 rounded-[var(--radius-sm)] px-(--menu-item-px) py-(--menu-item-py) text-(length:--menu-font-size) outline-none transition-colors focus:bg-foreground/[0.07] data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
131
131
  "data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive",
132
132
  inset && "pl-8",
133
133
  className
@@ -163,7 +163,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
163
163
  <DropdownMenuPrimitive.CheckboxItem
164
164
  ref={ref}
165
165
  className={cn(
166
- "relative flex cursor-default select-none items-center rounded-[var(--radius-sm)] py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
166
+ "relative flex cursor-default select-none items-center rounded-[var(--radius-sm)] py-(--menu-item-py) pl-8 pr-2 text-(length:--menu-font-size) outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
167
167
  className
168
168
  )}
169
169
  checked={checked}
@@ -187,7 +187,7 @@ const DropdownMenuRadioItem = React.forwardRef<
187
187
  <DropdownMenuPrimitive.RadioItem
188
188
  ref={ref}
189
189
  className={cn(
190
- "relative flex cursor-default select-none items-center rounded-[var(--radius-sm)] py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
190
+ "relative flex cursor-default select-none items-center rounded-[var(--radius-sm)] py-(--menu-item-py) pl-8 pr-2 text-(length:--menu-font-size) outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
191
191
  className
192
192
  )}
193
193
  {...props}
@@ -211,7 +211,7 @@ const DropdownMenuLabel = React.forwardRef<
211
211
  <DropdownMenuPrimitive.Label
212
212
  ref={ref}
213
213
  className={cn(
214
- "px-2 py-1.5 text-sm font-semibold",
214
+ "px-(--menu-item-px) py-(--menu-item-py) text-(length:--menu-label-font-size) font-(weight:--menu-label-weight) text-(color:--menu-label-color)",
215
215
  inset && "pl-8",
216
216
  className
217
217
  )}
@@ -76,7 +76,7 @@ const MenubarSubTrigger = React.forwardRef<
76
76
  <MenubarPrimitive.SubTrigger
77
77
  ref={ref}
78
78
  className={cn(
79
- "flex cursor-default select-none items-center rounded-[var(--radius-sm)] px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
79
+ "flex cursor-default select-none items-center rounded-[var(--radius-sm)] px-(--menu-item-px) py-(--menu-item-py) text-(length:--menu-font-size) outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
80
80
  inset && "pl-8",
81
81
  className
82
82
  )}
@@ -95,7 +95,7 @@ const MenubarSubContent = React.forwardRef<
95
95
  <MenubarPrimitive.SubContent
96
96
  ref={ref}
97
97
  className={cn(
98
- "z-[1400] min-w-32 overflow-hidden rounded-[var(--radius-popover)] border bg-popover backdrop-blur-xl p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
98
+ "z-[1400] min-w-32 overflow-hidden rounded-[var(--radius-popover)] border bg-popover backdrop-blur-xl p-(--menu-padding) text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
99
99
  className
100
100
  )}
101
101
  {...props}
@@ -118,7 +118,7 @@ const MenubarContent = React.forwardRef<
118
118
  alignOffset={alignOffset}
119
119
  sideOffset={sideOffset}
120
120
  className={cn(
121
- "z-[1400] min-w-48 overflow-hidden rounded-[var(--radius-popover)] border bg-popover backdrop-blur-xl p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
121
+ "z-[1400] min-w-48 overflow-hidden rounded-[var(--radius-popover)] border bg-popover backdrop-blur-xl p-(--menu-padding) text-popover-foreground shadow-(--popup-shadow) data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
122
122
  className
123
123
  )}
124
124
  {...props}
@@ -139,7 +139,7 @@ const MenubarItem = React.forwardRef<
139
139
  }
140
140
  >(({ className, inset, href, variant = "default", children, ...props }, ref) => {
141
141
  const classes = cn(
142
- "relative flex cursor-default select-none items-center rounded-[var(--radius-sm)] px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
142
+ "relative flex cursor-default select-none items-center rounded-[var(--radius-sm)] px-(--menu-item-px) py-(--menu-item-py) text-(length:--menu-font-size) outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
143
143
  "data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive",
144
144
  inset && "pl-8",
145
145
  className
@@ -175,7 +175,7 @@ const MenubarCheckboxItem = React.forwardRef<
175
175
  <MenubarPrimitive.CheckboxItem
176
176
  ref={ref}
177
177
  className={cn(
178
- "relative flex cursor-default select-none items-center rounded-[var(--radius-sm)] py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
178
+ "relative flex cursor-default select-none items-center rounded-[var(--radius-sm)] py-(--menu-item-py) pl-8 pr-2 text-(length:--menu-font-size) outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
179
179
  className
180
180
  )}
181
181
  checked={checked}
@@ -198,7 +198,7 @@ const MenubarRadioItem = React.forwardRef<
198
198
  <MenubarPrimitive.RadioItem
199
199
  ref={ref}
200
200
  className={cn(
201
- "relative flex cursor-default select-none items-center rounded-[var(--radius-sm)] py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
201
+ "relative flex cursor-default select-none items-center rounded-[var(--radius-sm)] py-(--menu-item-py) pl-8 pr-2 text-(length:--menu-font-size) outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
202
202
  className
203
203
  )}
204
204
  {...props}
@@ -222,7 +222,7 @@ const MenubarLabel = React.forwardRef<
222
222
  <MenubarPrimitive.Label
223
223
  ref={ref}
224
224
  className={cn(
225
- "px-2 py-1.5 text-sm font-semibold",
225
+ "px-(--menu-item-px) py-(--menu-item-py) text-(length:--menu-label-font-size) font-(weight:--menu-label-weight) text-(color:--menu-label-color)",
226
226
  inset && "pl-8",
227
227
  className
228
228
  )}
@@ -57,7 +57,7 @@ const PopoverContent = React.forwardRef<
57
57
  // (in the class list below) caps the whole panel to.
58
58
  collisionPadding={collisionPadding}
59
59
  className={cn(
60
- "popover-panel z-[1400] w-72 rounded-[var(--radius-popover)] border bg-popover backdrop-blur-xl p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
60
+ "popover-panel z-[1400] w-72 rounded-[var(--radius-popover)] border bg-popover backdrop-blur-xl p-4 text-popover-foreground shadow-(--popup-shadow) outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
61
61
  className
62
62
  )}
63
63
  {...props}
@@ -147,7 +147,7 @@ const SelectContent = React.forwardRef<
147
147
  <SelectPrimitive.Content
148
148
  ref={ref}
149
149
  className={cn(
150
- "relative z-[1400] max-h-(--radix-select-content-available-height) min-w-32 overflow-y-auto overflow-x-hidden rounded-[var(--radius-popover)] border bg-popover backdrop-blur-xl text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
150
+ "relative z-[1400] max-h-(--radix-select-content-available-height) min-w-32 overflow-y-auto overflow-x-hidden rounded-[var(--radius-popover)] border bg-popover backdrop-blur-xl text-popover-foreground shadow-(--popup-shadow) data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
151
151
  position === "popper" &&
152
152
  "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
153
153
  className
@@ -158,7 +158,7 @@ const SelectContent = React.forwardRef<
158
158
  <SelectScrollUpButton />
159
159
  <SelectPrimitive.Viewport
160
160
  className={cn(
161
- "p-1",
161
+ "p-(--menu-padding)",
162
162
  position === "popper" &&
163
163
  "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
164
164
  )}
@@ -177,7 +177,7 @@ const SelectLabel = React.forwardRef<
177
177
  >(({ className, ...props }, ref) => (
178
178
  <SelectPrimitive.Label
179
179
  ref={ref}
180
- className={cn("px-2 py-1.5 text-sm font-semibold", className)}
180
+ className={cn("px-(--menu-item-px) py-(--menu-item-py) text-(length:--menu-label-font-size) font-(weight:--menu-label-weight) text-(color:--menu-label-color)", className)}
181
181
  {...props}
182
182
  />
183
183
  ))
@@ -202,7 +202,7 @@ const SelectItem = React.forwardRef<
202
202
  ref={ref}
203
203
  value={value === '' ? EMPTY_SENTINEL : value}
204
204
  className={cn(
205
- "relative flex w-full cursor-default select-none items-center rounded-[var(--radius-sm)] py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
205
+ "relative flex w-full cursor-default select-none items-center rounded-[var(--radius-sm)] py-(--menu-item-py) pl-2 pr-8 text-(length:--menu-font-size) outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
206
206
  className
207
207
  )}
208
208
  {...props}
@@ -60,6 +60,63 @@
60
60
  --font-size-xl: 1.125rem;
61
61
  --line-height-base: 1.5;
62
62
  --letter-spacing-base: 0em;
63
+
64
+ /* Menu tokens — one density for dropdown, context menu, menubar and select panels.
65
+ * Components read these; a preset changes them in one place (never per-component literals). */
66
+ /* Chat prose follows the theme scale (Notion: 14px / 1.5). widget-chat reads these two. */
67
+ /* Notion-style tag colours: a tinted fill with matching ink (Badge variants gray…red). */
68
+ --tag-gray-bg: #e3e2e0;
69
+ --tag-gray-fg: #32302c;
70
+ --tag-brown-bg: #eee0da;
71
+ --tag-brown-fg: #442a1e;
72
+ --tag-orange-bg: #fadec9;
73
+ --tag-orange-fg: #49290e;
74
+ --tag-yellow-bg: #fdecc8;
75
+ --tag-yellow-fg: #402c1b;
76
+ --tag-green-bg: #dbeddb;
77
+ --tag-green-fg: #1c3829;
78
+ --tag-blue-bg: #d3e5ef;
79
+ --tag-blue-fg: #183347;
80
+ --tag-purple-bg: #e8deee;
81
+ --tag-purple-fg: #412454;
82
+ --tag-pink-bg: #f5e0e9;
83
+ --tag-pink-fg: #4c2337;
84
+ --tag-red-bg: #ffe2dd;
85
+ --tag-red-fg: #5d1715;
86
+ /* The control height ladder every Button, Input and Select reads (control-size.ts). */
87
+ --control-h-xs: 1.75rem;
88
+ --control-h-sm: 2rem;
89
+ --control-h-default: 2.25rem;
90
+ --control-h-lg: 2.75rem;
91
+ --control-h-huge: 3.5rem;
92
+ --button-weight: 600;
93
+ /* PrivateLayout sidebar rows (desktop). */
94
+ --sidebar-item-h: 2.25rem;
95
+ --sidebar-item-font: var(--font-size-sm);
96
+ --sidebar-item-radius: 0.5rem;
97
+ --sidebar-divider-h: 1px;
98
+ /* Badge metrics. Defaults keep the compact label; a preset widens them to a 20px tag. */
99
+ --badge-radius: var(--radius);
100
+ --badge-px: 0.625rem;
101
+ --badge-py: 0.125rem;
102
+ --badge-font-size: 0.75rem;
103
+ --badge-weight: 600;
104
+ --chat-text: var(--font-size-base);
105
+ --chat-leading: 1.5;
106
+ /* Side-panel title (widget-chat header): small caps by default, plain sentence case in Notion. */
107
+ --panel-title-weight: 600;
108
+ --panel-title-tracking: 0.025em;
109
+ --panel-title-color: var(--muted-foreground);
110
+ --panel-title-transform: uppercase;
111
+ /* Shadow of every popup surface (menus, select, popover). */
112
+ --popup-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
113
+ --menu-font-size: var(--font-size-sm);
114
+ --menu-label-font-size: var(--menu-font-size);
115
+ --menu-label-weight: 600;
116
+ --menu-label-color: inherit;
117
+ --menu-item-px: 0.5rem;
118
+ --menu-item-py: 0.375rem;
119
+ --menu-padding: 0.25rem;
63
120
  }
64
121
 
65
122
  /* Base element styles */
@@ -200,3 +257,25 @@ body {
200
257
  -webkit-font-smoothing: antialiased;
201
258
  -moz-osx-font-smoothing: grayscale;
202
259
  }
260
+
261
+ /* Tag colours on a dark page: translucent tints, near-white ink. */
262
+ .dark {
263
+ --tag-gray-bg: rgba(255,252,235,.306);
264
+ --tag-gray-fg: #f0efed;
265
+ --tag-brown-bg: rgba(210,160,120,.40);
266
+ --tag-brown-fg: #f0e6df;
267
+ --tag-orange-bg: rgba(255,146,60,.45);
268
+ --tag-orange-fg: #fbeadb;
269
+ --tag-yellow-bg: rgba(255,188,53,.46);
270
+ --tag-yellow-fg: #f9f3dc;
271
+ --tag-green-bg: rgba(113,255,175,.337);
272
+ --tag-green-fg: #e8f1ec;
273
+ --tag-blue-bg: rgba(81,166,255,.494);
274
+ --tag-blue-fg: #e5f2fc;
275
+ --tag-purple-bg: rgba(208,147,255,.427);
276
+ --tag-purple-fg: #f3ebf9;
277
+ --tag-pink-bg: rgba(255,133,192,.427);
278
+ --tag-pink-fg: #faE9f1;
279
+ --tag-red-bg: rgba(255,116,105,.525);
280
+ --tag-red-fg: #fce9e7;
281
+ }
@@ -0,0 +1,168 @@
1
+ /* Authoritative static preset: notion.
2
+ *
3
+ * Measured from app.notion.com on 2026-11-06 (its `:root, .notion-light-theme`
4
+ * custom properties), not eyeballed from a screenshot:
5
+ * canvas #fff · rail #f9f8f7 · hover #f0efed · hairline #e6e5e3
6
+ * ink #2c2c2b · secondary ink #7d7a75 · accent blue #2783de
7
+ * Notion is neutral: no brand colour on surfaces, one blue for the single
8
+ * primary action, and a warm-grey hover. Everything else is ink on white.
9
+ */
10
+ :root {
11
+ --background: hsl(0 0% 100%);
12
+ --foreground: hsl(60 1% 17%);
13
+ --card: hsl(0 0% 100%);
14
+ --card-foreground: hsl(60 1% 17%);
15
+ --popover: hsl(0 0% 100%);
16
+ --popover-foreground: hsl(60 1% 17%);
17
+ /* Notion's blue is #2783de (white on it: 3.9:1). One lightness step deeper
18
+ keeps white button labels at AA (4.6:1) while staying the same hue. */
19
+ --primary: hsl(209 74% 47%);
20
+ --primary-foreground: hsl(0 0% 100%);
21
+ /* #f9f8f7 — Notion's "secondary background". */
22
+ --secondary: hsl(30 14% 97%);
23
+ --secondary-foreground: hsl(60 1% 17%);
24
+ /* #f0efed. 6.5 points under the white page: a visible chip, not a plate. */
25
+ --muted: hsl(40 9% 93.5%);
26
+ /* #7d7a75 darkened one step: AA on white and on the muted fill. */
27
+ --muted-foreground: hsl(37 3% 43%);
28
+ --surface-hover: hsl(40 9% 95%);
29
+ /* Hover on the page: the same warm grey as Notion's row hover. */
30
+ --accent: hsl(40 9% 93.5%);
31
+ --accent-foreground: hsl(60 1% 17%);
32
+ /* #cf5148 text red, #e56458 fill red; deeper for white labels. */
33
+ --destructive: hsl(5 62% 48%);
34
+ --destructive-foreground: hsl(0 0% 100%);
35
+ --on-destructive: hsl(0 0% 100%);
36
+ /* #e6e5e3 */
37
+ --border: hsl(40 6% 90%);
38
+ /* Notion fields are white with a hairline, not a grey plate. */
39
+ --input: hsl(0 0% 100%);
40
+ --divider: hsl(40 6% 92%);
41
+ --ring: hsl(209 74% 52%);
42
+ --overlay: hsl(0 0% 0% / 0.4);
43
+ --radius: 0.375rem;
44
+ --shadow-card: 0 0 0 1px rgb(15 15 15 / 0.05), 0 3px 6px rgb(15 15 15 / 0.1);
45
+ /* Notion's sidebar is #f9f8f7 under #fff content; its hover is a ~8% ink
46
+ veil, 5 points down. */
47
+ --sidebar-background: hsl(30 14% 97%);
48
+ --sidebar-foreground: hsl(60 1% 17%);
49
+ --sidebar-primary: hsl(209 74% 47%);
50
+ --sidebar-primary-foreground: hsl(0 0% 100%);
51
+ --sidebar-accent: hsl(40 7% 92%);
52
+ --sidebar-accent-foreground: hsl(60 1% 17%);
53
+ --sidebar-border: hsl(40 6% 90%);
54
+ --sidebar-ring: hsl(209 74% 52%);
55
+ --code: hsl(30 14% 97%);
56
+ --code-foreground: hsl(60 1% 17%);
57
+ --code-border: hsl(40 6% 90%);
58
+ --code-inline: hsl(40 9% 93.5%);
59
+ --code-inline-foreground: hsl(5 62% 42%);
60
+ --chart-1: hsl(209 74% 52%);
61
+ --chart-2: hsl(147 30% 47%);
62
+ --chart-3: hsl(277 30% 55%);
63
+ --chart-4: hsl(31 65% 51%);
64
+ --chart-5: hsl(5 62% 59%);
65
+ /* Status tints are Notion's *BacPri / *TexPri / *BorPri triples. */
66
+ --warning: hsl(39 60% 51%);
67
+ --warning-background: hsl(52 70% 96%);
68
+ --warning-foreground: hsl(45 53% 26%);
69
+ --warning-border: hsl(46 66% 82%);
70
+ --success: hsl(147 30% 47%);
71
+ --success-background: hsl(147 20% 96%);
72
+ --success-foreground: hsl(147 33% 25%);
73
+ --success-border: hsl(147 22% 87%);
74
+ --destructive-background: hsl(0 55% 97%);
75
+ --destructive-border: hsl(5 70% 92%);
76
+ --info: hsl(209 74% 52%);
77
+ --info-background: hsl(209 80% 97%);
78
+ --info-foreground: hsl(209 50% 29%);
79
+ --info-border: hsl(209 75% 91%);
80
+ /* Notion types in the system UI stack, 14px, with a 1.5 leading. */
81
+ --font-sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI Variable Display", "Segoe UI", Helvetica, "Apple Color Emoji", Arial, sans-serif;
82
+ --font-size-base: 0.875rem;
83
+ --font-size-sm: 0.8125rem;
84
+ --font-size-xs: 0.75rem;
85
+ --line-height-base: 1.5;
86
+ /* Notion's menus: 14px type, 28px rows (4px vertical padding), 4px panel padding. */
87
+ /* Notion AI: messages 14px / 20px. */
88
+ /* Notion's controls are flat: the hairline does the work, no drop shadow. */
89
+ --shadow: 0 0 #0000;
90
+ --shadow-xs: 0 0 #0000;
91
+ --shadow-sm: 0 0 #0000;
92
+ /* Notion's controls are 24/28/32px, 14px type at weight 500. */
93
+ --control-h-xs: 1.5rem;
94
+ --control-h-sm: 1.75rem;
95
+ --control-h-default: 2rem;
96
+ --control-h-lg: 2.5rem;
97
+ --button-weight: 500;
98
+ /* Notion's sidebar row: 30px, 14px type, 6px corner. */
99
+ --sidebar-item-h: 1.875rem;
100
+ --sidebar-item-font: var(--font-size-base);
101
+ --sidebar-item-radius: 0.375rem;
102
+ /* Notion separates sidebar groups by space, not by a rule. */
103
+ --sidebar-divider-h: 0px;
104
+ /* Notion's top bar is 44px. */
105
+ --panel-header-h: 2.75rem;
106
+ /* Notion tags: 20px, 4px corner, 14px regular. */
107
+ --badge-radius: 0.25rem;
108
+ --badge-px: 0.375rem;
109
+ --badge-py: 0;
110
+ --badge-font-size: 0.875rem;
111
+ --badge-weight: 400;
112
+ --chat-text: var(--font-size-base);
113
+ --chat-leading: 1.5;
114
+ /* The AI composer is a raised 1px-ringed card (#202020 on #191919 in dark). */
115
+ --chat-composer-bg: var(--card);
116
+ --chat-composer-radius: 0.75rem;
117
+ --chat-composer-button-radius: 9999px;
118
+ --chat-composer-ring-w: 1px;
119
+ --chat-composer-ring: var(--border);
120
+ --chat-composer-ring-focus: var(--ring);
121
+ --panel-title-weight: 500;
122
+ --panel-title-tracking: 0;
123
+ --panel-title-color: var(--foreground);
124
+ --panel-title-transform: none;
125
+ --menu-font-size: var(--font-size-base);
126
+ /* Menu section labels: 12px / 500 in the muted ink. */
127
+ --menu-label-font-size: var(--font-size-xs);
128
+ --menu-label-weight: 500;
129
+ --menu-label-color: var(--muted-foreground);
130
+ --radius-popover: 0.625rem;
131
+ --popup-shadow: 0 0 0 1px rgb(15 15 15 / 0.05), 0 3px 6px rgb(15 15 15 / 0.1), 0 9px 24px rgb(15 15 15 / 0.2);
132
+ --menu-item-px: 0.5rem;
133
+ --menu-item-py: 0.25rem;
134
+ --menu-padding: 0.25rem;
135
+ }
136
+
137
+ /* Notion dark (#191919 page, #202020 rail, #2f2f2f hairline). */
138
+ .dark {
139
+ --background: hsl(0 0% 10%);
140
+ --foreground: hsl(40 9% 93.5%);
141
+ --card: hsl(0 0% 12%);
142
+ --card-foreground: hsl(40 9% 93.5%);
143
+ --popover: hsl(0 0% 15%);
144
+ --popover-foreground: hsl(40 9% 93.5%);
145
+ --primary: hsl(209 74% 52%);
146
+ --primary-foreground: hsl(0 0% 100%);
147
+ --secondary: hsl(0 0% 15%);
148
+ --secondary-foreground: hsl(0 0% 81%);
149
+ --muted: hsl(0 0% 15%);
150
+ --muted-foreground: hsl(0 0% 60%);
151
+ --accent: hsl(0 0% 17%);
152
+ --accent-foreground: hsl(0 0% 90%);
153
+ --border: hsl(0 0% 18%);
154
+ --input: hsl(0 0% 18%);
155
+ --divider: hsl(0 0% 15%);
156
+ --ring: hsl(209 74% 52%);
157
+ --sidebar-background: hsl(0 0% 12.5%);
158
+ --sidebar-foreground: hsl(0 0% 81%);
159
+ --sidebar-primary: hsl(209 74% 52%);
160
+ --sidebar-primary-foreground: hsl(0 0% 100%);
161
+ --sidebar-accent: hsl(0 0% 17%);
162
+ --sidebar-accent-foreground: hsl(0 0% 90%);
163
+ --sidebar-border: hsl(0 0% 18%);
164
+ --sidebar-ring: hsl(209 74% 52%);
165
+ --chart-1: hsl(209 74% 58%);
166
+ /* Dark popups: a 1px #383836 ring plus a soft drop. */
167
+ --popup-shadow: 0 0 0 1px hsl(60 2% 22%), 0 14px 28px -6px rgb(0 0 0 / 0.2), 0 2px 4px -1px rgb(0 0 0 / 0.12);
168
+ }
@@ -19,21 +19,21 @@
19
19
  * panels and menus read as raised. muted recesses below the page.
20
20
  * page ≈ bg-100 card ≈ bg-000(18.4%) popover 18.8% (claude.ai menus) */
21
21
  --background: hsl(0 0% 10%);
22
- --foreground: hsl(0 0% 97.6%);
23
- --card: hsl(0 0% 13%);
24
- --card-foreground: hsl(0 0% 97.6%);
25
- --popover: hsl(0 0% 18.8%);
26
- --popover-foreground: hsl(0 0% 97.6%);
22
+ --foreground: hsl(0 0% 81%);
23
+ --card: hsl(0 0% 12%);
24
+ --card-foreground: hsl(0 0% 81%);
25
+ --popover: hsl(0 0% 15%);
26
+ --popover-foreground: hsl(0 0% 81%);
27
27
  /* Brand clay #d97757 (Claude). Bright enough on dark chrome to carry the full swatch. */
28
- --primary: hsl(15 63% 60%);
28
+ --primary: hsl(209 74% 52%);
29
29
  /* Dark label on clay — 5.8:1, stronger than white-on-clay (3.1:1) */
30
- --primary-foreground: hsl(0 0% 9%);
30
+ --primary-foreground: hsl(0 0% 100%);
31
31
  /* Secondary: elevated warm-gray control surface + light label */
32
- --secondary: hsl(0 0% 17%);
33
- --secondary-foreground: hsl(0 0% 97.6%);
32
+ --secondary: hsl(0 0% 15%);
33
+ --secondary-foreground: hsl(0 0% 81%);
34
34
  /* Recessed surface — below the page (Claude bg-200), for input rest / chips. */
35
- --muted: hsl(0 0% 8%);
36
- --muted-foreground: hsl(0 0% 62%);
35
+ --muted: hsl(0 0% 15%);
36
+ --muted-foreground: hsl(0 0% 60%);
37
37
  /* Neutral interaction surface: a quiet lift above the current dark page. */
38
38
  --surface-hover: color-mix(in oklab, var(--card) 96%, var(--foreground));
39
39
  /* The counterpart to --surface-hover: a surface that reads as sunk BELOW the
@@ -49,8 +49,8 @@
49
49
  * and composites over whatever surface the band happens to sit on. */
50
50
  --surface-recessed: rgb(0 0 0 / 0.35);
51
51
  /* Hover/active surface (rails, menus, tabs) — a quiet warm lift above the page. */
52
- --accent: hsl(0 0% 15.5%);
53
- --accent-foreground: hsl(0 0% 97.6%);
52
+ --accent: hsl(0 0% 17%);
53
+ --accent-foreground: hsl(0 0% 90%);
54
54
  --destructive: hsl(0 67% 60%);
55
55
  --destructive-foreground: hsl(0 0% 98%);
56
56
  /* Soft warm border — Claude draws hairlines from a light border at low
@@ -61,7 +61,7 @@
61
61
  Capped by MAXIMUM_BORDER_GAP in check-preset-contrast.mjs. */
62
62
  --border: hsl(0 0% 18%);
63
63
  /* Input surface — a notch above card so fields read as raised controls. */
64
- --input: hsl(0 0% 17%);
64
+ --input: hsl(0 0% 18%);
65
65
  /* Divider — TRANSLUCENT hairline (Apple-style): a light warm-gray at low alpha
66
66
  * that dissolves into whatever sits behind it, so structural separators
67
67
  * (columns, headers, rows) stay quiet on the dark page instead of reading as a
@@ -72,12 +72,12 @@
72
72
  left of "кондово" after the fills were calmed. Over a 12% card this
73
73
  lands at 15.4% (step 3.4) against the border's 18% (step 6.0): a clear
74
74
  hierarchy, frame first. */
75
- --divider: hsl(0 0% 100% / 0.08);
75
+ --divider: hsl(0 0% 15%);
76
76
  /* Overlay — modal scrim / backdrop behind dialogs, drawers, sheets. Black in
77
77
  * both themes; slightly darker here so it still reads on the dark page. */
78
78
  --overlay: hsl(0 0% 0% / 0.7);
79
79
  /* Focus ring — blue (system-accent feel), independent of the cyan brand. */
80
- --ring: hsl(217 91% 60%);
80
+ --ring: hsl(209 74% 52%);
81
81
  --shadow-card: none;
82
82
 
83
83
  /* Code surface — see light.css for the rationale. On dark we want
@@ -94,13 +94,13 @@
94
94
  * block; rail and page share one tone). A hair darker than --background for
95
95
  * subtle separation, not a hard black slab. */
96
96
  --sidebar-background: hsl(0 0% 12.5%);
97
- --sidebar-foreground: hsl(0 0% 97.6%);
98
- --sidebar-primary: hsl(15 63% 60%);
99
- --sidebar-primary-foreground: hsl(0 0% 9%);
100
- --sidebar-accent: hsl(0 0% 19%);
101
- --sidebar-accent-foreground: hsl(0 0% 97.6%);
97
+ --sidebar-foreground: hsl(0 0% 81%);
98
+ --sidebar-primary: hsl(209 74% 52%);
99
+ --sidebar-primary-foreground: hsl(0 0% 100%);
100
+ --sidebar-accent: hsl(0 0% 17%);
101
+ --sidebar-accent-foreground: hsl(0 0% 90%);
102
102
  --sidebar-border: hsl(0 0% 18%);
103
- --sidebar-ring: hsl(217 91% 60%);
103
+ --sidebar-ring: hsl(209 74% 52%);
104
104
  /* ── Status surfaces (dark) ──────────────────────────────────────
105
105
  * Dark variants: dim backgrounds (~8-12% lightness) so banners
106
106
  * don't blow out against the near-black page. Foreground raised
@@ -148,7 +148,7 @@
148
148
  /* Chart colors — chart-1 aligned with brand.
149
149
  * Fully-wrapped hsl() like every other token (consume via `bg-chart-N` or
150
150
  * `var(--chart-N)`, never `hsl(var(--chart-N))`). */
151
- --chart-1: hsl(15 63% 60%);
151
+ --chart-1: hsl(209 74% 58%);
152
152
  --chart-2: hsl(142 76% 36%);
153
153
  --chart-3: hsl(262 83% 58%);
154
154
  --chart-4: hsl(26 90% 57%);
@@ -1,13 +1,8 @@
1
1
  /**
2
- * Light Theme Colors
2
+ * Light Theme Colors — Notion-neutral (retuned 2026-11-06 from app.notion.com's own custom properties).
3
3
  *
4
- * Brand: Claude "clay" #d97757 (hsl 14.77 63% 60%). On **white** the 60% swatch with a white label
5
- * fails contrast, so `:root` `--primary` uses a deeper clay (`hsl 15 55% 45%`, white-on-fill 5.0:1 AA)
6
- * for buttons, links, and `text-primary`. The full-brightness clay is reserved for brand marks / dark
7
- * UI; the dark theme carries the brighter swatch via `.dark`.
8
- *
9
- * Neutrals follow Claude's current theme: a faintly WARM off-white canvas (hue ~60, not pure gray) so
10
- * cards and chips read against the page the way they do on claude.ai.
4
+ * White canvas, #f9f8f7 rail, warm-grey hover, one blue for the primary action. The previous Claude "clay"
5
+ * brand orange is gone from surfaces; the `notion` preset is a pinned copy of these values for side-by-side checks.
11
6
  *
12
7
  * Pairs `--primary` / `--primary-foreground` are tuned together. Prefer editing tokens over ad hoc classes.
13
8
  */
@@ -17,23 +12,23 @@
17
12
  * fills and a white raised card. Keep `muted` below `background` in light mode
18
13
  * so chips, message bubbles, and secondary controls never dissolve into the
19
14
  * page. */
20
- --background: hsl(60 14% 97%);
21
- --foreground: hsl(0 0% 7.5%);
15
+ --background: hsl(0 0% 100%);
16
+ --foreground: hsl(60 1% 17%);
22
17
  --card: hsl(0 0% 100%);
23
- --card-foreground: hsl(0 0% 7.5%);
18
+ --card-foreground: hsl(60 1% 17%);
24
19
  --popover: hsl(0 0% 100%);
25
- --popover-foreground: hsl(0 0% 7.5%);
20
+ --popover-foreground: hsl(60 1% 17%);
26
21
  /* Interactive primary on white — deep clay (~#b6543a): readable white-on-fill (5.0:1) + calmer than the brand swatch */
27
- --primary: hsl(15 55% 45%);
22
+ --primary: hsl(209 74% 47%);
28
23
  --primary-foreground: hsl(0 0% 100%);
29
- --secondary: hsl(0 0% 7.5%);
30
- --secondary-foreground: hsl(60 14% 97%);
31
- --muted: hsl(52 12% 93%);
32
- --muted-foreground: hsl(48 3% 46%);
24
+ --secondary: hsl(30 14% 97%);
25
+ --secondary-foreground: hsl(60 1% 17%);
26
+ --muted: hsl(40 9% 93.5%);
27
+ --muted-foreground: hsl(37 3% 43%);
33
28
  /* Neutral interaction surface: a quiet step below the current light canvas.
34
29
  * It deliberately keys off --background, not white --card, so hover remains
35
30
  * visible on translucent menus and on plain pages alike. */
36
- --surface-hover: color-mix(in oklab, var(--background) 96%, var(--foreground));
31
+ --surface-hover: hsl(40 9% 95%);
37
32
  /* The counterpart to --surface-hover: a surface that reads as sunk BELOW the
38
33
  * page. A translucent black for the same reason as the dark theme's, but much
39
34
  * weaker: the same overlay costs far more contrast on a near-white ground,
@@ -41,23 +36,23 @@
41
36
  --surface-recessed: rgb(0 0 0 / 0.045);
42
37
  /* Neutral hover/active surface — quiet warm-gray lift (Claude bg-200), a
43
38
  * clear step (≥4L) below the 97% canvas. */
44
- --accent: hsl(60 11% 92%);
45
- --accent-foreground: hsl(0 0% 7.5%);
39
+ --accent: hsl(40 9% 93.5%);
40
+ --accent-foreground: hsl(60 1% 17%);
46
41
  /* Destructive — Claude danger-100 hsl(0 61% 52%). Slightly deeper for a solid
47
42
  * white-on-fill AA pass. */
48
- --destructive: hsl(0 61% 50%);
49
- --destructive-foreground: hsl(0 0% 98%);
50
- --border: hsl(50 10% 86%);
43
+ --destructive: hsl(5 62% 48%);
44
+ --destructive-foreground: hsl(0 0% 100%);
45
+ --border: hsl(40 6% 90%);
51
46
  /* Input surface — faint warm fill so fields are visible on white cards. */
52
- --input: hsl(60 14% 97%);
47
+ --input: hsl(40 6% 90%);
53
48
  /* Divider — hairline between rows; slightly darker than --border so it reads
54
49
  * on white cards. */
55
- --divider: hsl(50 9% 87%);
50
+ --divider: hsl(40 6% 92%);
56
51
  /* Overlay — modal scrim / backdrop behind dialogs, drawers, sheets. A scrim
57
52
  * is black in both themes; the token owns the opacity. */
58
- --overlay: hsl(0 0% 0% / 0.6);
53
+ --overlay: hsl(0 0% 0% / 0.4);
59
54
  /* Focus ring — blue (system-accent feel), independent of the cyan brand. */
60
- --ring: hsl(217 91% 55%);
55
+ --ring: hsl(209 74% 52%);
61
56
  --shadow-card: 0 1px 3px 0 rgb(0 0 0 / 0.06), 0 1px 2px -1px rgb(0 0 0 / 0.04);
62
57
 
63
58
  /* Code surface — used by markdown code fences, terminal blocks,
@@ -70,23 +65,23 @@
70
65
  * for body legibility. Border keeps the panel anchored.
71
66
  * Inline code shares the same surface for chip-on-chip cohesion.
72
67
  */
73
- --code: hsl(0 0% 96%);
74
- --code-foreground: hsl(0 0% 12%);
75
- --code-border: hsl(0 0% 88%);
76
- --code-inline: hsl(0 0% 93%);
77
- --code-inline-foreground: hsl(0 0% 14%);
68
+ --code: hsl(30 14% 97%);
69
+ --code-foreground: hsl(60 1% 17%);
70
+ --code-border: hsl(40 6% 90%);
71
+ --code-inline: hsl(40 9% 93.5%);
72
+ --code-inline-foreground: hsl(5 62% 42%);
78
73
  /* Sidebar Light Theme — warm off-white, same family as the page (Claude keeps
79
74
  * rail and page in one tone). */
80
- --sidebar-background: hsl(60 14% 96%);
81
- --sidebar-foreground: hsl(0 0% 7.5%);
82
- --sidebar-primary: hsl(15 55% 45%);
75
+ --sidebar-background: hsl(30 14% 97%);
76
+ --sidebar-foreground: hsl(60 1% 17%);
77
+ --sidebar-primary: hsl(209 74% 47%);
83
78
  --sidebar-primary-foreground: hsl(0 0% 100%);
84
79
  /* 90%, a clear (≥4L) step below the 96% rail — hover stays visible. See the
85
80
  2026-08-03 note: the dark pair steps ~12 points, so light must step too. */
86
- --sidebar-accent: hsl(52 12% 90%);
87
- --sidebar-accent-foreground: hsl(0 0% 7.5%);
88
- --sidebar-border: hsl(50 10% 89%);
89
- --sidebar-ring: hsl(217 91% 55%);
81
+ --sidebar-accent: hsl(40 7% 92%);
82
+ --sidebar-accent-foreground: hsl(60 1% 17%);
83
+ --sidebar-border: hsl(40 6% 90%);
84
+ --sidebar-ring: hsl(209 74% 52%);
90
85
  /* ── Status surfaces ─────────────────────────────────────────────
91
86
  * Each status has 4 tokens: icon/accent color, page background,
92
87
  * readable foreground text, and border ring.
@@ -100,23 +95,23 @@
100
95
  * ─────────────────────────────────────────────────────────────── */
101
96
 
102
97
  /* Warning — amber */
103
- --warning: hsl(38 92% 50%);
104
- --warning-background: hsl(48 100% 96%);
105
- --warning-foreground: hsl(26 90% 25%);
106
- --warning-border: hsl(38 70% 65%);
98
+ --warning: hsl(39 60% 51%);
99
+ --warning-background: hsl(52 70% 96%);
100
+ --warning-foreground: hsl(45 53% 26%);
101
+ --warning-border: hsl(46 66% 82%);
107
102
  /* Success — green */
108
- --success: hsl(142 71% 45%);
109
- --success-background: hsl(138 76% 97%);
110
- --success-foreground: hsl(140 60% 20%);
111
- --success-border: hsl(142 60% 65%);
103
+ --success: hsl(147 30% 47%);
104
+ --success-background: hsl(147 20% 96%);
105
+ --success-foreground: hsl(147 33% 25%);
106
+ --success-border: hsl(147 22% 87%);
112
107
  /* Destructive surface (separate from the filled button token) */
113
- --destructive-background: hsl(0 100% 97%);
114
- --destructive-border: hsl(0 84% 70%);
108
+ --destructive-background: hsl(0 55% 97%);
109
+ --destructive-border: hsl(5 70% 92%);
115
110
  /* Info — blue-cyan (brand-adjacent) */
116
- --info: hsl(200 90% 40%);
117
- --info-background: hsl(200 100% 97%);
118
- --info-foreground: hsl(200 80% 20%);
119
- --info-border: hsl(200 80% 65%);
111
+ --info: hsl(209 74% 52%);
112
+ --info-background: hsl(209 80% 97%);
113
+ --info-foreground: hsl(209 50% 29%);
114
+ --info-border: hsl(209 75% 91%);
120
115
 
121
116
  /* On-fill text/icon — readable color sitting ON the solid `*` fill
122
117
  * (badges, unread pills, filled chips), distinct from the `*-foreground`
@@ -129,16 +124,16 @@
129
124
  --on-success: hsl(0 0% 9%);
130
125
  --on-warning: hsl(0 0% 9%);
131
126
  --on-info: hsl(0 0% 9%);
132
- --on-destructive: hsl(0 0% 9%);
127
+ --on-destructive: hsl(0 0% 100%);
133
128
  /* Surface gradient */
134
129
  --surface-gradient: linear-gradient(to bottom, var(--background), color-mix(in oklab, var(--background) 80%, transparent));
135
130
 
136
131
  /* Chart colors — chart-1 aligned with brand hue.
137
132
  * Fully-wrapped hsl() like every other token (consume via `bg-chart-N` or
138
133
  * `var(--chart-N)`, never `hsl(var(--chart-N))`). */
139
- --chart-1: hsl(15 55% 45%);
140
- --chart-2: hsl(142 76% 36%);
141
- --chart-3: hsl(262 83% 58%);
142
- --chart-4: hsl(26 90% 57%);
143
- --chart-5: hsl(346 77% 50%);
134
+ --chart-1: hsl(209 74% 52%);
135
+ --chart-2: hsl(147 30% 47%);
136
+ --chart-3: hsl(277 30% 55%);
137
+ --chart-4: hsl(31 65% 51%);
138
+ --chart-5: hsl(5 62% 59%);
144
139
  }
@@ -3,6 +3,7 @@ import type { ThemeStylePresetId } from './types';
3
3
  export const THEME_STYLE_PRESET_ORDER = [
4
4
  'default',
5
5
  'django-cfg',
6
+ 'notion',
6
7
  'ios',
7
8
  'macos',
8
9
  'windows',
@@ -13,4 +13,5 @@ export type ThemeStylePresetId =
13
13
  | 'windows'
14
14
  | 'soft'
15
15
  | 'dense'
16
- | 'high-contrast';
16
+ | 'high-contrast'
17
+ | 'notion';