[{"data":1,"prerenderedAt":1596},["ShallowReactive",2],{"article-tags":3,"latest-articles-5":85,"published-articles":106,"article-categories":198,"/article/tailwind-css-variables":204},[4,7,9,12,14,18,20,22,24,27,30,32,34,36,38,41,43,46,48,50,52,54,56,58,60,62,65,68,71,73,75,77,79,81,83],{"name":5,"slug":5,"count":6},"beginner",4,{"name":8,"slug":8,"count":6},"linux",{"name":10,"slug":11,"count":6},"nuxt.js","nuxt-js",{"name":13,"slug":13,"count":6},"vim",{"name":15,"slug":16,"count":17},"@nuxt/content","nuxt-content",2,{"name":19,"slug":19,"count":17},"cloudflare",{"name":21,"slug":21,"count":17},"mysql",{"name":23,"slug":23,"count":17},"tools",{"name":25,"slug":25,"count":26},"architecture",1,{"name":28,"slug":29,"count":26},"AWS","aws",{"name":31,"slug":31,"count":26},"biome",{"name":33,"slug":33,"count":26},"book",{"name":35,"slug":35,"count":26},"css",{"name":37,"slug":37,"count":26},"eslint",{"name":39,"slug":40,"count":26},"ExpoGo","expogo",{"name":42,"slug":42,"count":26},"git",{"name":44,"slug":45,"count":26},"github action","github-action",{"name":47,"slug":47,"count":26},"index",{"name":49,"slug":49,"count":26},"ios",{"name":51,"slug":51,"count":26},"markdown",{"name":53,"slug":53,"count":26},"neovim",{"name":55,"slug":55,"count":26},"obsidian",{"name":57,"slug":57,"count":26},"php",{"name":59,"slug":59,"count":26},"plugins",{"name":61,"slug":61,"count":26},"programing",{"name":63,"slug":64,"count":26},"RDS","rds",{"name":66,"slug":67,"count":26},"ReactNative","reactnative",{"name":69,"slug":70,"count":26},"S3","s3",{"name":72,"slug":72,"count":26},"shell",{"name":74,"slug":74,"count":26},"tailwindcss",{"name":76,"slug":76,"count":26},"terminal",{"name":78,"slug":78,"count":26},"tips",{"name":80,"slug":80,"count":26},"tool",{"name":82,"slug":82,"count":26},"trigger",{"name":84,"slug":84,"count":26},"zsh",[86,90,94,98,102],{"path":87,"title":88,"date":89},"/article/mysql-before-delete-trigger","MySQLのBEFORE DELETEトリガーをちゃんと調べる","2026-09-14",{"path":91,"title":92,"date":93},"/article/eslint-biome-migration","ESLintとPrettierをBiomeやoxlintに乗り換えられるか試す","2026-09-07",{"path":95,"title":96,"date":97},"/article/cloudflare-trailing-slash","Cloudflare Pagesが付ける末尾スラッシュで、記事が「無い」ことになる","2026-09-02",{"path":99,"title":100,"date":101},"/article/tailwind-css-variables","Tailwindのトークンを1箇所にまとめても、ダークモードは切り替わらない","2026-09-01",{"path":103,"title":104,"date":105},"/article/s3-to-rds","AmazonS3のファイルをRDSにロードして使う","2026-02-18",[107,110,112,114,116,118,123,128,133,138,143,148,153,158,163,168,173,178,183,188,193],{"path":87,"title":88,"date":89,"tags":108,"category":109},[21,82],"blog",{"path":91,"title":92,"date":93,"tags":111,"category":109},[37,31,10],{"path":95,"title":96,"date":97,"tags":113,"category":109},[19,10,15],{"path":99,"title":100,"date":101,"tags":115,"category":109},[74,10,35],{"path":103,"title":104,"date":105,"tags":117,"category":109},[28,69,63],{"path":119,"title":120,"date":121,"tags":122,"category":109},"/article/tools-lazygit","おすすめツール紹介 - lazygit編","2026-02-10",[80,42,76],{"path":124,"title":125,"date":126,"tags":127,"category":109},"/article/nuxt-content-cloudflare","Nuxt Contentの記事がSSR時に取得できない問題とその解決策","2026-02-07",[10,19,15],{"path":129,"title":130,"date":131,"tags":132,"category":109},"/article/linux-fg-bg","fgとbgでプロセスを操る","2026-02-04",[8],{"path":134,"title":135,"date":136,"tags":137,"category":33},"/article/architecture-textbook","【書評】アーキテクトの教科書","2026-01-30",[33,25],{"path":139,"title":140,"date":141,"tags":142,"category":109},"/article/shell-dev-null","Linuxの標準出力をきちんと理解する","2026-01-28",[72,8],{"path":144,"title":145,"date":146,"tags":147,"category":109},"/article/shell-utilities","シェルの小技・今後使っていきたい機能","2026-01-27",[84,8],{"path":149,"title":150,"date":151,"tags":152,"category":109},"/article/obsidian-action","obsidianのリポジトリをGithubActionsで自動整形する","2026-01-26",[55,44],{"path":154,"title":155,"date":156,"tags":157,"category":109},"/article/react-native-reduce","ReactNativeでのiOS実機確認(Expo Go)で、アプリが動かない事象とその解決法","2026-01-25",[66,39,49],{"path":159,"title":160,"date":161,"tags":162,"category":109},"/article/php-null","phpのnull合体演算子を深掘りする","2026-01-24",[57,61],{"path":164,"title":165,"date":166,"tags":167,"category":109},"/article/mysql-index","6,500万件のテーブルにindexを貼る","2026-01-22",[21,47],{"path":169,"title":170,"date":171,"tags":172,"category":109},"/article/render-markdown","render-markdown.nvimでlinkがうまくレンダリングされなくなった","2026-01-18",[53,59,5],{"path":174,"title":175,"date":176,"tags":177,"category":109},"/article/vim-abbreviation","vimで略語展開設定してみる","2026-01-17",[13,78,5],{"path":179,"title":180,"date":181,"tags":182,"category":109},"/article/vim-folding","neovimのmarkdown折りたたみを改造した","2026-01-16",[13,51],{"path":184,"title":185,"date":186,"tags":187,"category":109},"/article/archlinux-install"," Arch Linux インストールの悪戦苦闘メモ","2026-01-14",[8,5],{"path":189,"title":190,"date":191,"tags":192,"category":109},"/article/vim-standard-memo","Vim標準機能で使いたいものメモ","2026-01-11",[13,23],{"path":194,"title":195,"date":196,"tags":197,"category":109},"/article/vim-command","vim標準で使いたいコマンド","2026-01-10",[13,23,5],[199,202],{"slug":109,"label":200,"count":201},"Blog",20,{"slug":33,"label":203,"count":26},"Books",{"id":205,"title":100,"body":206,"category":109,"date":101,"description":1589,"extension":1590,"meta":1591,"navigation":339,"path":99,"published":339,"seo":1592,"stem":1593,"tags":1594,"__hash__":1595},"article/article/tailwind-css-variables.md",{"type":207,"value":208,"toc":1581},"minimark",[209,213,216,221,237,450,564,577,584,613,623,626,637,665,688,691,755,761,772,776,785,865,875,886,897,1293,1314,1317,1389,1399,1403,1409,1426,1469,1481,1490,1525,1543,1546,1552,1561,1564,1577],[210,211,212],"p",{},"このブログにダークテーマを入れた。作業自体は数時間で終わっている。",[210,214,215],{},"ただ、この短さは当日の実装がうまくいったからではない。前日に「色の定義を1箇所にまとめる」リファクタを済ませていて、しかもそれだけでは足りず、もう一段の作り直しが挟まっている。難所は実装ではなく、その手前にあった。",[217,218,220],"h2",{"id":219},"トークンを1箇所にまとめてもテーマは切り替わらない","トークンを1箇所にまとめても、テーマは切り替わらない",[210,222,223,224,228,229,232,233,236],{},"前日のリファクタでやったのは、",[225,226,227],"code",{},"tailwind.config.ts"," と レイアウトの ",[225,230,231],{},":root"," に別々に書かれていた色を、",[225,234,235],{},"theme/tokens.ts"," に集約することだった。",[238,239,244],"pre",{"className":240,"code":241,"language":242,"meta":243,"style":243},"language-ts shiki shiki-themes github-light github-dark","// theme/tokens.ts\nexport const colors = {\n  main: '#1A1A1A',\n  accent: '#8B5CF6',\n  border: '#E5E5E5',\n  'surface-subtle': '#F5F5F5',\n} as const\n\nexport function toCssVariables(): Record\u003Cstring, string> {\n  return Object.fromEntries(\n    Object.entries(colors).map(([name, value]) => [`--color-${name}`, value]),\n  )\n}\n","ts","",[225,245,246,254,274,287,297,308,322,334,341,376,391,438,444],{"__ignoreMap":243},[247,248,250],"span",{"class":249,"line":26},"line",[247,251,253],{"class":252},"sJ8bj","// theme/tokens.ts\n",[247,255,256,260,263,267,270],{"class":249,"line":17},[247,257,259],{"class":258},"szBVR","export",[247,261,262],{"class":258}," const",[247,264,266],{"class":265},"sj4cs"," colors",[247,268,269],{"class":258}," =",[247,271,273],{"class":272},"sVt8B"," {\n",[247,275,277,280,284],{"class":249,"line":276},3,[247,278,279],{"class":272},"  main: ",[247,281,283],{"class":282},"sZZnC","'#1A1A1A'",[247,285,286],{"class":272},",\n",[247,288,289,292,295],{"class":249,"line":6},[247,290,291],{"class":272},"  accent: ",[247,293,294],{"class":282},"'#8B5CF6'",[247,296,286],{"class":272},[247,298,300,303,306],{"class":249,"line":299},5,[247,301,302],{"class":272},"  border: ",[247,304,305],{"class":282},"'#E5E5E5'",[247,307,286],{"class":272},[247,309,311,314,317,320],{"class":249,"line":310},6,[247,312,313],{"class":282},"  'surface-subtle'",[247,315,316],{"class":272},": ",[247,318,319],{"class":282},"'#F5F5F5'",[247,321,286],{"class":272},[247,323,325,328,331],{"class":249,"line":324},7,[247,326,327],{"class":272},"} ",[247,329,330],{"class":258},"as",[247,332,333],{"class":258}," const\n",[247,335,337],{"class":249,"line":336},8,[247,338,340],{"emptyLinePlaceholder":339},true,"\n",[247,342,344,346,349,353,356,359,362,365,368,371,373],{"class":249,"line":343},9,[247,345,259],{"class":258},[247,347,348],{"class":258}," function",[247,350,352],{"class":351},"sScJk"," toCssVariables",[247,354,355],{"class":272},"()",[247,357,358],{"class":258},":",[247,360,361],{"class":351}," Record",[247,363,364],{"class":272},"\u003C",[247,366,367],{"class":265},"string",[247,369,370],{"class":272},", ",[247,372,367],{"class":265},[247,374,375],{"class":272},"> {\n",[247,377,379,382,385,388],{"class":249,"line":378},10,[247,380,381],{"class":258},"  return",[247,383,384],{"class":272}," Object.",[247,386,387],{"class":351},"fromEntries",[247,389,390],{"class":272},"(\n",[247,392,394,397,400,403,406,409,413,415,418,421,424,427,430,432,435],{"class":249,"line":393},11,[247,395,396],{"class":272},"    Object.",[247,398,399],{"class":351},"entries",[247,401,402],{"class":272},"(colors).",[247,404,405],{"class":351},"map",[247,407,408],{"class":272},"(([",[247,410,412],{"class":411},"s4XuR","name",[247,414,370],{"class":272},[247,416,417],{"class":411},"value",[247,419,420],{"class":272},"]) ",[247,422,423],{"class":258},"=>",[247,425,426],{"class":272}," [",[247,428,429],{"class":282},"`--color-${",[247,431,412],{"class":272},[247,433,434],{"class":282},"}`",[247,436,437],{"class":272},", value]),\n",[247,439,441],{"class":249,"line":440},12,[247,442,443],{"class":272},"  )\n",[247,445,447],{"class":249,"line":446},13,[247,448,449],{"class":272},"}\n",[238,451,453],{"className":240,"code":452,"language":242,"meta":243,"style":243},"// tailwind.config.ts\nconst cssVariables = plugin(({ addBase }) => {\n  addBase({ ':root': toCssVariables() })\n})\n\nexport default \u003CConfig>{\n  theme: {\n    extend: {\n      colors,\n    },\n  },\n  plugins: [cssVariables],\n}\n",[225,454,455,460,486,505,510,514,530,535,540,545,550,555,560],{"__ignoreMap":243},[247,456,457],{"class":249,"line":26},[247,458,459],{"class":252},"// tailwind.config.ts\n",[247,461,462,465,468,470,473,476,479,482,484],{"class":249,"line":17},[247,463,464],{"class":258},"const",[247,466,467],{"class":265}," cssVariables",[247,469,269],{"class":258},[247,471,472],{"class":351}," plugin",[247,474,475],{"class":272},"(({ ",[247,477,478],{"class":411},"addBase",[247,480,481],{"class":272}," }) ",[247,483,423],{"class":258},[247,485,273],{"class":272},[247,487,488,491,494,497,499,502],{"class":249,"line":276},[247,489,490],{"class":351},"  addBase",[247,492,493],{"class":272},"({ ",[247,495,496],{"class":282},"':root'",[247,498,316],{"class":272},[247,500,501],{"class":351},"toCssVariables",[247,503,504],{"class":272},"() })\n",[247,506,507],{"class":249,"line":6},[247,508,509],{"class":272},"})\n",[247,511,512],{"class":249,"line":299},[247,513,340],{"emptyLinePlaceholder":339},[247,515,516,518,521,524,527],{"class":249,"line":310},[247,517,259],{"class":258},[247,519,520],{"class":258}," default",[247,522,523],{"class":272}," \u003C",[247,525,526],{"class":351},"Config",[247,528,529],{"class":272},">{\n",[247,531,532],{"class":249,"line":324},[247,533,534],{"class":272},"  theme: {\n",[247,536,537],{"class":249,"line":336},[247,538,539],{"class":272},"    extend: {\n",[247,541,542],{"class":249,"line":343},[247,543,544],{"class":272},"      colors,\n",[247,546,547],{"class":249,"line":378},[247,548,549],{"class":272},"    },\n",[247,551,552],{"class":249,"line":393},[247,553,554],{"class":272},"  },\n",[247,556,557],{"class":249,"line":440},[247,558,559],{"class":272},"  plugins: [cssVariables],\n",[247,561,562],{"class":249,"line":446},[247,563,449],{"class":272},[210,565,566,568,569,572,573,576],{},[225,567,231],{}," のCSS変数も、",[225,570,571],{},"text-main"," のようなユーティリティクラスも、どちらも ",[225,574,575],{},"tokens.ts"," から生成される。二重管理は消えた。",[210,578,579,580,583],{},"ここまで来れば、あとは ",[225,581,582],{},".dark"," で変数を上書きすれば全体が切り替わる。そう思っていた。",[238,585,588],{"className":586,"code":587,"language":35,"meta":243,"style":243},"language-css shiki shiki-themes github-light github-dark",".dark {\n  --color-main: #E5E5E5;\n}\n",[225,589,590,596,609],{"__ignoreMap":243},[247,591,592,594],{"class":249,"line":26},[247,593,582],{"class":351},[247,595,273],{"class":272},[247,597,598,601,603,606],{"class":249,"line":17},[247,599,600],{"class":411},"  --color-main",[247,602,316],{"class":272},[247,604,605],{"class":265},"#E5E5E5",[247,607,608],{"class":272},";\n",[247,610,611],{"class":249,"line":276},[247,612,449],{"class":272},[210,614,615,616,619,620,622],{},"切り替わったのは ",[225,617,618],{},"var(--color-main)"," と直接書いた箇所だけで、",[225,621,571],{}," は黒のままだった。",[217,624,625],{"id":625},"ユーティリティクラスにはhexが焼き込まれている",[210,627,628,629,632,633,636],{},"Tailwindはユーティリティクラスをビルド時に生成する。",[225,630,631],{},"extend.colors"," に渡した ",[225,634,635],{},"colors"," は生のhexのオブジェクトなので、出てくるCSSはこうなる。",[238,638,640],{"className":586,"code":639,"language":35,"meta":243,"style":243},".text-main {\n  color: #1A1A1A;\n}\n",[225,641,642,649,661],{"__ignoreMap":243},[247,643,644,647],{"class":249,"line":26},[247,645,646],{"class":351},".text-main",[247,648,273],{"class":272},[247,650,651,654,656,659],{"class":249,"line":17},[247,652,653],{"class":265},"  color",[247,655,316],{"class":272},[247,657,658],{"class":265},"#1A1A1A",[247,660,608],{"class":272},[247,662,663],{"class":249,"line":276},[247,664,449],{"class":272},[210,666,667,670,671,673,674,676,677,679,680,684,685,687],{},[225,668,669],{},"var()"," がどこにもない。",[225,672,231],{}," の変数と ",[225,675,646],{}," の値はどちらも ",[225,678,575],{}," から出ているのに、",[681,682,683],"strong",{},"生成された時点で切り離されている","。実行時に変数を上書きしても、",[225,686,646],{}," はそれを見ていない。",[210,689,690],{},"同じことがTailwindの設定の中でも起きていた。typographyプラグインの設定では、色をテンプレートリテラルで埋め込んでいる。",[238,692,694],{"className":240,"code":693,"language":242,"meta":243,"style":243},"pre: {\n  backgroundColor: colors['surface-subtle'],\n  color: '#24292E',\n  border: `1px solid ${colors.border}`,\n},\n",[225,695,696,703,717,728,750],{"__ignoreMap":243},[247,697,698,700],{"class":249,"line":26},[247,699,238],{"class":351},[247,701,702],{"class":272},": {\n",[247,704,705,708,711,714],{"class":249,"line":17},[247,706,707],{"class":351},"  backgroundColor",[247,709,710],{"class":272},": colors[",[247,712,713],{"class":282},"'surface-subtle'",[247,715,716],{"class":272},"],\n",[247,718,719,721,723,726],{"class":249,"line":276},[247,720,653],{"class":351},[247,722,316],{"class":272},[247,724,725],{"class":282},"'#24292E'",[247,727,286],{"class":272},[247,729,730,733,735,738,740,743,746,748],{"class":249,"line":6},[247,731,732],{"class":351},"  border",[247,734,316],{"class":272},[247,736,737],{"class":282},"`1px solid ${",[247,739,635],{"class":272},[247,741,742],{"class":282},".",[247,744,745],{"class":272},"border",[247,747,434],{"class":282},[247,749,286],{"class":272},[247,751,752],{"class":249,"line":299},[247,753,754],{"class":272},"},\n",[210,756,757,758,760],{},"これも生成時に確定する。",[225,759,575],{}," を参照してはいるが、出てくるのはhexの文字列でしかない。",[762,763,766],"callout",{"title":764,"type":765},"「単一情報源にした」と「実行時に切り替わる」は別の話","warning",[210,767,768,769,771],{},"前者はビルド時の入力を1つにすること、後者は出力されたCSSが ",[225,770,669],{}," を参照していること。\n前者だけを満たしても、テーマは切り替わらない。",[217,773,775],{"id":774},"参照をcss変数に変える","参照をCSS変数に変える",[210,777,778,779,781,782,784],{},"やることは単純で、",[225,780,631],{}," にhexではなく ",[225,783,669],{}," を渡す。",[238,786,788],{"className":240,"code":787,"language":242,"meta":243,"style":243},"export function toTailwindColors(): Record\u003Cstring, string> {\n  return Object.fromEntries(\n    Object.entries(colors).map(([name]) => [name, `var(--color-${name})`]),\n  )\n}\n",[225,789,790,815,825,857,861],{"__ignoreMap":243},[247,791,792,794,796,799,801,803,805,807,809,811,813],{"class":249,"line":26},[247,793,259],{"class":258},[247,795,348],{"class":258},[247,797,798],{"class":351}," toTailwindColors",[247,800,355],{"class":272},[247,802,358],{"class":258},[247,804,361],{"class":351},[247,806,364],{"class":272},[247,808,367],{"class":265},[247,810,370],{"class":272},[247,812,367],{"class":265},[247,814,375],{"class":272},[247,816,817,819,821,823],{"class":249,"line":17},[247,818,381],{"class":258},[247,820,384],{"class":272},[247,822,387],{"class":351},[247,824,390],{"class":272},[247,826,827,829,831,833,835,837,839,841,843,846,849,851,854],{"class":249,"line":276},[247,828,396],{"class":272},[247,830,399],{"class":351},[247,832,402],{"class":272},[247,834,405],{"class":351},[247,836,408],{"class":272},[247,838,412],{"class":411},[247,840,420],{"class":272},[247,842,423],{"class":258},[247,844,845],{"class":272}," [name, ",[247,847,848],{"class":282},"`var(--color-${",[247,850,412],{"class":272},[247,852,853],{"class":282},"})`",[247,855,856],{"class":272},"]),\n",[247,858,859],{"class":249,"line":6},[247,860,443],{"class":272},[247,862,863],{"class":249,"line":299},[247,864,449],{"class":272},[210,866,867,868,871,872,874],{},"これで ",[225,869,870],{},".text-main { color: var(--color-main) }"," が出力され、",[225,873,582],{}," の上書きが効くようになる。",[210,876,877,878,881,882,885],{},"ただしこれだけだと不透明度修飾子が壊れる。",[225,879,880],{},"bg-accent/10"," はTailwindが色の値からアルファ付きの色を組み立てる書き方だが、",[225,883,884],{},"var(--color-accent)"," はTailwindから見ると中身の分からない文字列なので、そこにアルファを差し込めない。",[210,887,888,889,892,893,896],{},"Tailwindはこのために ",[225,890,891],{},"\u003Calpha-value>"," というプレースホルダを用意している。定義にこれを含めておくと、修飾子の値がその位置に差し込まれる。差し込む先を作るには、色を ",[225,894,895],{},"26 26 26"," のような空白区切りのチャンネル値でも持っておく必要がある。",[238,898,900],{"className":240,"code":899,"language":242,"meta":243,"style":243},"const isHex = (value: string) => value.startsWith('#')\n\nfunction hexToRgbChannels(hex: string): string {\n  return [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16)).join(' ')\n}\n\nexport function toCssVariables(): Record\u003Cstring, string> {\n  return Object.fromEntries(\n    Object.entries(colors).flatMap(([name, value]) => [\n      [`--color-${name}`, value],\n      ...(isHex(value) ? [[`--color-${name}-rgb`, hexToRgbChannels(value)]] : []),\n    ]),\n  )\n}\n\nexport function toTailwindColors(): Record\u003Cstring, string> {\n  return Object.fromEntries(\n    Object.entries(colors).map(([name, value]) => [\n      name,\n      isHex(value) ? `rgb(var(--color-${name}-rgb) / \u003Calpha-value>)` : `var(--color-${name})`,\n    ]),\n  )\n}\n",[225,901,902,941,945,971,1042,1046,1050,1074,1084,1110,1124,1163,1168,1172,1177,1182,1207,1218,1243,1249,1278,1283,1288],{"__ignoreMap":243},[247,903,904,906,909,911,914,916,918,921,924,926,929,932,935,938],{"class":249,"line":26},[247,905,464],{"class":258},[247,907,908],{"class":351}," isHex",[247,910,269],{"class":258},[247,912,913],{"class":272}," (",[247,915,417],{"class":411},[247,917,358],{"class":258},[247,919,920],{"class":265}," string",[247,922,923],{"class":272},") ",[247,925,423],{"class":258},[247,927,928],{"class":272}," value.",[247,930,931],{"class":351},"startsWith",[247,933,934],{"class":272},"(",[247,936,937],{"class":282},"'#'",[247,939,940],{"class":272},")\n",[247,942,943],{"class":249,"line":17},[247,944,340],{"emptyLinePlaceholder":339},[247,946,947,950,953,955,958,960,962,965,967,969],{"class":249,"line":276},[247,948,949],{"class":258},"function",[247,951,952],{"class":351}," hexToRgbChannels",[247,954,934],{"class":272},[247,956,957],{"class":411},"hex",[247,959,358],{"class":258},[247,961,920],{"class":265},[247,963,964],{"class":272},")",[247,966,358],{"class":258},[247,968,920],{"class":265},[247,970,273],{"class":272},[247,972,973,975,977,980,982,985,987,990,993,995,998,1001,1003,1005,1008,1011,1014,1017,1020,1023,1026,1029,1032,1035,1037,1040],{"class":249,"line":6},[247,974,381],{"class":258},[247,976,426],{"class":272},[247,978,979],{"class":265},"1",[247,981,370],{"class":272},[247,983,984],{"class":265},"3",[247,986,370],{"class":272},[247,988,989],{"class":265},"5",[247,991,992],{"class":272},"].",[247,994,405],{"class":351},[247,996,997],{"class":272},"((",[247,999,1000],{"class":411},"i",[247,1002,923],{"class":272},[247,1004,423],{"class":258},[247,1006,1007],{"class":351}," parseInt",[247,1009,1010],{"class":272},"(hex.",[247,1012,1013],{"class":351},"slice",[247,1015,1016],{"class":272},"(i, i ",[247,1018,1019],{"class":258},"+",[247,1021,1022],{"class":265}," 2",[247,1024,1025],{"class":272},"), ",[247,1027,1028],{"class":265},"16",[247,1030,1031],{"class":272},")).",[247,1033,1034],{"class":351},"join",[247,1036,934],{"class":272},[247,1038,1039],{"class":282},"' '",[247,1041,940],{"class":272},[247,1043,1044],{"class":249,"line":299},[247,1045,449],{"class":272},[247,1047,1048],{"class":249,"line":310},[247,1049,340],{"emptyLinePlaceholder":339},[247,1051,1052,1054,1056,1058,1060,1062,1064,1066,1068,1070,1072],{"class":249,"line":324},[247,1053,259],{"class":258},[247,1055,348],{"class":258},[247,1057,352],{"class":351},[247,1059,355],{"class":272},[247,1061,358],{"class":258},[247,1063,361],{"class":351},[247,1065,364],{"class":272},[247,1067,367],{"class":265},[247,1069,370],{"class":272},[247,1071,367],{"class":265},[247,1073,375],{"class":272},[247,1075,1076,1078,1080,1082],{"class":249,"line":336},[247,1077,381],{"class":258},[247,1079,384],{"class":272},[247,1081,387],{"class":351},[247,1083,390],{"class":272},[247,1085,1086,1088,1090,1092,1095,1097,1099,1101,1103,1105,1107],{"class":249,"line":343},[247,1087,396],{"class":272},[247,1089,399],{"class":351},[247,1091,402],{"class":272},[247,1093,1094],{"class":351},"flatMap",[247,1096,408],{"class":272},[247,1098,412],{"class":411},[247,1100,370],{"class":272},[247,1102,417],{"class":411},[247,1104,420],{"class":272},[247,1106,423],{"class":258},[247,1108,1109],{"class":272}," [\n",[247,1111,1112,1115,1117,1119,1121],{"class":249,"line":378},[247,1113,1114],{"class":272},"      [",[247,1116,429],{"class":282},[247,1118,412],{"class":272},[247,1120,434],{"class":282},[247,1122,1123],{"class":272},", value],\n",[247,1125,1126,1129,1131,1134,1137,1140,1143,1145,1147,1150,1152,1155,1158,1160],{"class":249,"line":393},[247,1127,1128],{"class":258},"      ...",[247,1130,934],{"class":272},[247,1132,1133],{"class":351},"isHex",[247,1135,1136],{"class":272},"(value) ",[247,1138,1139],{"class":258},"?",[247,1141,1142],{"class":272}," [[",[247,1144,429],{"class":282},[247,1146,412],{"class":272},[247,1148,1149],{"class":282},"}-rgb`",[247,1151,370],{"class":272},[247,1153,1154],{"class":351},"hexToRgbChannels",[247,1156,1157],{"class":272},"(value)]] ",[247,1159,358],{"class":258},[247,1161,1162],{"class":272}," []),\n",[247,1164,1165],{"class":249,"line":440},[247,1166,1167],{"class":272},"    ]),\n",[247,1169,1170],{"class":249,"line":446},[247,1171,443],{"class":272},[247,1173,1175],{"class":249,"line":1174},14,[247,1176,449],{"class":272},[247,1178,1180],{"class":249,"line":1179},15,[247,1181,340],{"emptyLinePlaceholder":339},[247,1183,1185,1187,1189,1191,1193,1195,1197,1199,1201,1203,1205],{"class":249,"line":1184},16,[247,1186,259],{"class":258},[247,1188,348],{"class":258},[247,1190,798],{"class":351},[247,1192,355],{"class":272},[247,1194,358],{"class":258},[247,1196,361],{"class":351},[247,1198,364],{"class":272},[247,1200,367],{"class":265},[247,1202,370],{"class":272},[247,1204,367],{"class":265},[247,1206,375],{"class":272},[247,1208,1210,1212,1214,1216],{"class":249,"line":1209},17,[247,1211,381],{"class":258},[247,1213,384],{"class":272},[247,1215,387],{"class":351},[247,1217,390],{"class":272},[247,1219,1221,1223,1225,1227,1229,1231,1233,1235,1237,1239,1241],{"class":249,"line":1220},18,[247,1222,396],{"class":272},[247,1224,399],{"class":351},[247,1226,402],{"class":272},[247,1228,405],{"class":351},[247,1230,408],{"class":272},[247,1232,412],{"class":411},[247,1234,370],{"class":272},[247,1236,417],{"class":411},[247,1238,420],{"class":272},[247,1240,423],{"class":258},[247,1242,1109],{"class":272},[247,1244,1246],{"class":249,"line":1245},19,[247,1247,1248],{"class":272},"      name,\n",[247,1250,1251,1254,1256,1258,1261,1263,1266,1269,1272,1274,1276],{"class":249,"line":201},[247,1252,1253],{"class":351},"      isHex",[247,1255,1136],{"class":272},[247,1257,1139],{"class":258},[247,1259,1260],{"class":282}," `rgb(var(--color-${",[247,1262,412],{"class":272},[247,1264,1265],{"class":282},"}-rgb) / \u003Calpha-value>)`",[247,1267,1268],{"class":258}," :",[247,1270,1271],{"class":282}," `var(--color-${",[247,1273,412],{"class":272},[247,1275,853],{"class":282},[247,1277,286],{"class":272},[247,1279,1281],{"class":249,"line":1280},21,[247,1282,1167],{"class":272},[247,1284,1286],{"class":249,"line":1285},22,[247,1287,443],{"class":272},[247,1289,1291],{"class":249,"line":1290},23,[247,1292,449],{"class":272},[210,1294,1295,1296,1299,1300,1303,1304,1306,1307,1310,1311,1313],{},"トークン1つにつき ",[225,1297,1298],{},"--color-accent"," と ",[225,1301,1302],{},"--color-accent-rgb"," の2本を出す。前者はCSSから直接 ",[225,1305,669],{}," で使う分、後者はTailwindが組み立てる分。",[225,1308,1309],{},"rgba(0, 0, 0, 0.5)"," のようなhexでない値にはチャンネル版を作れないので、",[225,1312,1133],{}," で分岐している。",[210,1315,1316],{},"出力はこうなる。",[238,1318,1320],{"className":586,"code":1319,"language":35,"meta":243,"style":243},".bg-accent      { background-color: rgb(var(--color-accent-rgb) / 1); }\n.bg-accent\\/10  { background-color: rgb(var(--color-accent-rgb) / 0.1); }\n",[225,1321,1322,1355],{"__ignoreMap":243},[247,1323,1324,1327,1330,1333,1335,1338,1340,1343,1345,1347,1350,1352],{"class":249,"line":26},[247,1325,1326],{"class":351},".bg-accent",[247,1328,1329],{"class":272},"      { ",[247,1331,1332],{"class":265},"background-color",[247,1334,316],{"class":272},[247,1336,1337],{"class":265},"rgb",[247,1339,934],{"class":272},[247,1341,1342],{"class":265},"var",[247,1344,934],{"class":272},[247,1346,1302],{"class":411},[247,1348,1349],{"class":272},") / ",[247,1351,979],{"class":265},[247,1353,1354],{"class":272},"); }\n",[247,1356,1357,1359,1362,1365,1368,1370,1372,1374,1376,1378,1380,1382,1384,1387],{"class":249,"line":17},[247,1358,1326],{"class":351},[247,1360,1361],{"class":265},"\\/",[247,1363,1364],{"class":351},"10",[247,1366,1367],{"class":272},"  { ",[247,1369,1332],{"class":265},[247,1371,316],{"class":272},[247,1373,1337],{"class":265},[247,1375,934],{"class":272},[247,1377,1342],{"class":265},[247,1379,934],{"class":272},[247,1381,1302],{"class":411},[247,1383,1349],{"class":272},[247,1385,1386],{"class":265},"0.1",[247,1388,1354],{"class":272},[210,1390,1391,1392,1395,1396,1398],{},"あとは ",[225,1393,1394],{},"darkMode: 'class'"," を足して、",[225,1397,582],{}," に暗い側の値を並べればテーマ全体が切り替わる。",[217,1400,1402],{"id":1401},"パレット外の色は変数化しても残る","パレット外の色は、変数化しても残る",[210,1404,1405,1406,1408],{},"ここまでで ",[225,1407,575],{}," に載っている色は全部切り替わるようになる。裏を返すと、載っていない色は切り替わらない。",[210,1410,1411,1414,1415,1418,1419,1421,1422,1425],{},[225,1412,1413],{},"bg-white"," や ",[225,1416,1417],{},"bg-gray-100"," はTailwindの組み込みパレットなので、",[225,1420,575],{}," の管轄外にある。CSSに直接書いた ",[225,1423,1424],{},"#fff"," も同じ。当時これらをコンポーネント9ファイルで使っていた。",[238,1427,1431],{"className":1428,"code":1429,"language":1430,"meta":243,"style":243},"language-html shiki shiki-themes github-light github-dark","\u003Cdiv class=\"... bg-white border border-border ...\">\n  \u003Cdiv class=\"... bg-gray-100\">\n","html",[225,1432,1433,1453],{"__ignoreMap":243},[247,1434,1435,1437,1441,1444,1447,1450],{"class":249,"line":26},[247,1436,364],{"class":272},[247,1438,1440],{"class":1439},"s9eBZ","div",[247,1442,1443],{"class":351}," class",[247,1445,1446],{"class":272},"=",[247,1448,1449],{"class":282},"\"... bg-white border border-border ...\"",[247,1451,1452],{"class":272},">\n",[247,1454,1455,1458,1460,1462,1464,1467],{"class":249,"line":17},[247,1456,1457],{"class":272},"  \u003C",[247,1459,1440],{"class":1439},[247,1461,1443],{"class":351},[247,1463,1446],{"class":272},[247,1465,1466],{"class":282},"\"... bg-gray-100\"",[247,1468,1452],{"class":272},[210,1470,1471,1474,1475,1477,1478,1480],{},[225,1472,1473],{},"border-border"," は ",[225,1476,575],{}," 由来なので切り替わるが、",[225,1479,1413],{}," は白のまま残る。ダークにすると、同じカードの中で背景と枠線が食い違う。",[210,1482,1483,1299,1486,1489],{},[225,1484,1485],{},"surface",[225,1487,1488],{},"surface-muted"," をトークンとして新設して、置き換えた。",[238,1491,1493],{"className":1428,"code":1492,"language":1430,"meta":243,"style":243},"\u003Cdiv class=\"... bg-surface border border-border ...\">\n  \u003Cdiv class=\"... bg-surface-muted\">\n",[225,1494,1495,1510],{"__ignoreMap":243},[247,1496,1497,1499,1501,1503,1505,1508],{"class":249,"line":26},[247,1498,364],{"class":272},[247,1500,1440],{"class":1439},[247,1502,1443],{"class":351},[247,1504,1446],{"class":272},[247,1506,1507],{"class":282},"\"... bg-surface border border-border ...\"",[247,1509,1452],{"class":272},[247,1511,1512,1514,1516,1518,1520,1523],{"class":249,"line":17},[247,1513,1457],{"class":272},[247,1515,1440],{"class":1439},[247,1517,1443],{"class":351},[247,1519,1446],{"class":272},[247,1521,1522],{"class":282},"\"... bg-surface-muted\"",[247,1524,1452],{"class":272},[210,1526,1527,1528,1530,1531,1533,1534,1533,1537,1533,1539,1542],{},"この洗い出しは自動化できなかった。",[225,1529,575],{}," を読んでも「使われていない色」は見えないし、grepするにしても ",[225,1532,1413],{}," ",[225,1535,1536],{},"bg-gray-*",[225,1538,1424],{},[225,1540,1541],{},"#000"," と探す対象を人が列挙することになる。結局コンポーネントを一通り目で見た。",[217,1544,1545],{"id":1545},"おわりに",[210,1547,1548,1549,1551],{},"ダークテーマそのものの実装は、色が変数になってさえいれば ",[225,1550,582],{}," の値を書くだけで終わる。時間がかかるのはその手前、色の参照が本当に一本化されているかのほうだった。",[210,1553,1554,1555,1557,1558,1560],{},"確認は難しくない。DevToolsで ",[225,1556,646],{}," の中身を見て、hexが出ていたらまだ土台はできていない。",[225,1559,669],{}," が出ていれば、あとは値を差し替えるだけになる。",[217,1562,1563],{"id":1563},"参考",[1565,1566,1567],"ul",{},[1568,1569,1570],"li",{},[1571,1572,1576],"a",{"href":1573,"rel":1574},"https://v3.tailwindcss.com/docs/customizing-colors#using-css-variables",[1575],"nofollow","Using CSS variables - Tailwind CSS",[1578,1579,1580],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":243,"searchDepth":17,"depth":17,"links":1582},[1583,1584,1585,1586,1587,1588],{"id":219,"depth":17,"text":220},{"id":625,"depth":17,"text":625},{"id":774,"depth":17,"text":775},{"id":1401,"depth":17,"text":1402},{"id":1545,"depth":17,"text":1545},{"id":1563,"depth":17,"text":1563},"ダークテーマが簡単に入るかどうかは、実装当日ではなく、色の参照をCSS変数に一本化できているかで決まっている","md",{},{"title":100,"description":1589},"article/tailwind-css-variables",[74,10,35],"C1pjGLapeza09BZRRxrxjcJEogM7myFZWIJ-I0AtBMM",1790473632649]