Skip to content

Commit 74342af

Browse files
fix(web): support opacity modifiers for theme colors
1 parent 08cf01d commit 74342af

2 files changed

Lines changed: 113 additions & 73 deletions

File tree

packages/web/tailwind.config.ts

Lines changed: 76 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import type { Config } from "tailwindcss"
22

3+
const withOpacity = (variable: `--${string}`) =>
4+
`color-mix(in srgb, var(${variable}) calc(<alpha-value> * 100%), transparent)`
5+
36
const config = {
47
darkMode: ["class"],
58
content: [
@@ -19,103 +22,103 @@ const config = {
1922
},
2023
extend: {
2124
colors: {
22-
border: 'var(--border)',
23-
input: 'var(--input)',
24-
ring: 'var(--ring)',
25-
background: 'var(--background)',
26-
backgroundSecondary: 'var(--background-secondary)',
27-
foreground: 'var(--foreground)',
25+
border: withOpacity('--border'),
26+
input: withOpacity('--input'),
27+
ring: withOpacity('--ring'),
28+
background: withOpacity('--background'),
29+
backgroundSecondary: withOpacity('--background-secondary'),
30+
foreground: withOpacity('--foreground'),
2831
primary: {
29-
DEFAULT: 'var(--primary)',
30-
foreground: 'var(--primary-foreground)'
32+
DEFAULT: withOpacity('--primary'),
33+
foreground: withOpacity('--primary-foreground')
3134
},
3235
secondary: {
33-
DEFAULT: 'var(--secondary)',
34-
foreground: 'var(--secondary-foreground)'
36+
DEFAULT: withOpacity('--secondary'),
37+
foreground: withOpacity('--secondary-foreground')
3538
},
3639
destructive: {
37-
DEFAULT: 'var(--destructive)',
38-
foreground: 'var(--destructive-foreground)'
40+
DEFAULT: withOpacity('--destructive'),
41+
foreground: withOpacity('--destructive-foreground')
3942
},
4043
muted: {
41-
DEFAULT: 'var(--muted)',
42-
foreground: 'var(--muted-foreground)',
43-
accent: 'var(--muted-accent)'
44+
DEFAULT: withOpacity('--muted'),
45+
foreground: withOpacity('--muted-foreground'),
46+
accent: withOpacity('--muted-accent')
4447
},
4548
accent: {
46-
DEFAULT: 'var(--accent)',
47-
foreground: 'var(--accent-foreground)'
49+
DEFAULT: withOpacity('--accent'),
50+
foreground: withOpacity('--accent-foreground')
4851
},
4952
popover: {
50-
DEFAULT: 'var(--popover)',
51-
foreground: 'var(--popover-foreground)'
53+
DEFAULT: withOpacity('--popover'),
54+
foreground: withOpacity('--popover-foreground')
5255
},
5356
card: {
54-
DEFAULT: 'var(--card)',
55-
foreground: 'var(--card-foreground)'
57+
DEFAULT: withOpacity('--card'),
58+
foreground: withOpacity('--card-foreground')
5659
},
57-
highlight: 'var(--highlight)',
58-
link: 'var(--link)',
59-
shell: 'var(--shell)',
60+
highlight: withOpacity('--highlight'),
61+
link: withOpacity('--link'),
62+
shell: withOpacity('--shell'),
6063
sidebar: {
61-
DEFAULT: 'var(--sidebar-background)',
62-
foreground: 'var(--sidebar-foreground)',
63-
primary: 'var(--sidebar-primary)',
64-
'primary-foreground': 'var(--sidebar-primary-foreground)',
65-
accent: 'var(--sidebar-accent)',
66-
'accent-foreground': 'var(--sidebar-accent-foreground)',
67-
border: 'var(--sidebar-border)',
68-
ring: 'var(--sidebar-ring)'
64+
DEFAULT: withOpacity('--sidebar-background'),
65+
foreground: withOpacity('--sidebar-foreground'),
66+
primary: withOpacity('--sidebar-primary'),
67+
'primary-foreground': withOpacity('--sidebar-primary-foreground'),
68+
accent: withOpacity('--sidebar-accent'),
69+
'accent-foreground': withOpacity('--sidebar-accent-foreground'),
70+
border: withOpacity('--sidebar-border'),
71+
ring: withOpacity('--sidebar-ring')
6972
},
70-
warning: 'var(--warning)',
71-
error: 'var(--error)',
73+
warning: withOpacity('--warning'),
74+
error: withOpacity('--error'),
7275
editor: {
73-
background: 'var(--editor-background)',
74-
foreground: 'var(--editor-foreground)',
75-
caret: 'var(--editor-caret)',
76-
selection: 'var(--editor-selection)',
77-
selectionMatch: 'var(--editor-selection-match)',
78-
gutterBackground: 'var(--editor-gutter-background)',
79-
gutterForeground: 'var(--editor-gutter-foreground)',
76+
background: withOpacity('--editor-background'),
77+
foreground: withOpacity('--editor-foreground'),
78+
caret: withOpacity('--editor-caret'),
79+
selection: withOpacity('--editor-selection'),
80+
selectionMatch: withOpacity('--editor-selection-match'),
81+
gutterBackground: withOpacity('--editor-gutter-background'),
82+
gutterForeground: withOpacity('--editor-gutter-foreground'),
8083
gutterBorder: 'var(--editor-gutter-border)',
81-
gutterActiveForeground: 'var(--editor-gutter-active-foreground)',
82-
lineHighlight: 'var(--editor-line-highlight)',
84+
gutterActiveForeground: withOpacity('--editor-gutter-active-foreground'),
85+
lineHighlight: withOpacity('--editor-line-highlight'),
8386
tag: {
84-
keyword: 'var(--editor-tag-keyword)',
85-
name: 'var(--editor-tag-name)',
86-
function: 'var(--editor-tag-function)',
87-
label: 'var(--editor-tag-label)',
88-
constant: 'var(--editor-tag-constant)',
89-
definition: 'var(--editor-tag-definition)',
90-
brace: 'var(--editor-tag-brace)',
91-
type: 'var(--editor-tag-type)',
92-
operator: 'var(--editor-tag-operator)',
93-
tag: 'var(--editor-tag-tag)',
94-
'bracket-square': 'var(--editor-tag-bracket-square)',
95-
'bracket-angle': 'var(--editor-tag-bracket-angle)',
96-
attribute: 'var(--editor-tag-attribute)',
97-
string: 'var(--editor-tag-string)',
87+
keyword: withOpacity('--editor-tag-keyword'),
88+
name: withOpacity('--editor-tag-name'),
89+
function: withOpacity('--editor-tag-function'),
90+
label: withOpacity('--editor-tag-label'),
91+
constant: withOpacity('--editor-tag-constant'),
92+
definition: withOpacity('--editor-tag-definition'),
93+
brace: withOpacity('--editor-tag-brace'),
94+
type: withOpacity('--editor-tag-type'),
95+
operator: withOpacity('--editor-tag-operator'),
96+
tag: withOpacity('--editor-tag-tag'),
97+
'bracket-square': withOpacity('--editor-tag-bracket-square'),
98+
'bracket-angle': withOpacity('--editor-tag-bracket-angle'),
99+
attribute: withOpacity('--editor-tag-attribute'),
100+
string: withOpacity('--editor-tag-string'),
98101
link: 'var(--editor-tag-link)',
99-
meta: 'var(--editor-tag-meta)',
100-
comment: 'var(--editor-tag-comment)',
102+
meta: withOpacity('--editor-tag-meta'),
103+
comment: withOpacity('--editor-tag-comment'),
101104
emphasis: 'var(--editor-tag-emphasis)',
102105
heading: 'var(--editor-tag-heading)',
103-
atom: 'var(--editor-tag-atom)',
104-
processing: 'var(--editor-tag-processing)',
105-
separator: 'var(--editor-tag-separator)',
106-
invalid: 'var(--editor-tag-invalid)',
107-
quote: 'var(--editor-tag-quote)',
108-
'annotation-special': 'var(--editor-tag-annotation-special)',
109-
number: 'var(--editor-tag-number)',
110-
regexp: 'var(--editor-tag-regexp)',
111-
'variable-local': 'var(--editor-tag-variable-local)'
106+
atom: withOpacity('--editor-tag-atom'),
107+
processing: withOpacity('--editor-tag-processing'),
108+
separator: withOpacity('--editor-tag-separator'),
109+
invalid: withOpacity('--editor-tag-invalid'),
110+
quote: withOpacity('--editor-tag-quote'),
111+
'annotation-special': withOpacity('--editor-tag-annotation-special'),
112+
number: withOpacity('--editor-tag-number'),
113+
regexp: withOpacity('--editor-tag-regexp'),
114+
'variable-local': withOpacity('--editor-tag-variable-local')
112115
}
113116
},
114117
chat: {
115-
reference: 'var(--chat-reference)',
116-
'reference-hover': 'var(--chat-reference-hover)',
117-
'reference-selected': 'var(--chat-reference-selected)',
118-
'reference-selected-border': 'var(--chat-reference-selected-border)'
118+
reference: withOpacity('--chat-reference'),
119+
'reference-hover': withOpacity('--chat-reference-hover'),
120+
'reference-selected': withOpacity('--chat-reference-selected'),
121+
'reference-selected-border': withOpacity('--chat-reference-selected-border')
119122
}
120123
},
121124
fontSize: {
@@ -166,4 +169,4 @@ const config = {
166169
],
167170
} satisfies Config
168171

169-
export default config
172+
export default config
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import postcss from "postcss";
2+
import tailwindcss from "tailwindcss";
3+
import { describe, expect, test } from "vitest";
4+
5+
import tailwindConfig from "./tailwind.config";
6+
7+
const compileUtilities = async (classes: string) => {
8+
const result = await postcss([
9+
tailwindcss({
10+
...tailwindConfig,
11+
content: [{ raw: `<div class="${classes}"></div>` }],
12+
}),
13+
]).process("@tailwind utilities;", { from: undefined });
14+
15+
return result.css;
16+
};
17+
18+
describe("Tailwind theme colors", () => {
19+
test("applies opacity modifiers to CSS variable colors", async () => {
20+
const css = await compileUtilities(
21+
"text-primary text-primary/80 bg-warning/10 border-border/30",
22+
);
23+
24+
expect(css).toContain(
25+
"color: color-mix(in srgb, var(--primary) calc(var(--tw-text-opacity, 1) * 100%), transparent)",
26+
);
27+
expect(css).toContain(
28+
"color: color-mix(in srgb, var(--primary) calc(0.8 * 100%), transparent)",
29+
);
30+
expect(css).toContain(
31+
"background-color: color-mix(in srgb, var(--warning) calc(0.1 * 100%), transparent)",
32+
);
33+
expect(css).toContain(
34+
"border-color: color-mix(in srgb, var(--border) calc(0.3 * 100%), transparent)",
35+
);
36+
});
37+
});

0 commit comments

Comments
 (0)