February 23, 2026
CSS vars to TS vars
Code following make Javascript and Typescript file declaration to store design tokens callable in Frontend Project
*Code generated by ChatGPT. I didn't check if it work accuracy, but it work for me for now.
Install dependency
postcss
Convert to JS
import fs from "node:fs";
import path from "node:path";
import postcss from "postcss";
/** --kebab-case → camelCase */
function kebabToCamel(cssVar) {
return cssVar
.replace(/^--/, "")
.replace(/-([a-zA-Z0-9])/g, (_, c) => c.toUpperCase());
}
/**
* Extract CSS var declarations from file
*/
function extractCssVars(filePath, selectors = [":root"]) {
const css = fs.readFileSync(filePath, "utf8");
const root = postcss.parse(css);
/** @type {string[]} */
const names = [];
root.walkRules((rule) => {
const sels = rule.selectors || [];
if (!sels.some((s) => selectors.includes(s))) return;
rule.walkDecls((decl) => {
if (decl.prop && decl.prop.startsWith("--")) {
names.push(decl.prop);
}
});
});
return names;
}
async function run({
input = "styles/tokens.css",
outFile = "src/styles/tokens.js",
selectors = [":root"],
allowlist = null,
} = {}) {
const inPath = path.resolve(process.cwd(), input);
const outPath = path.resolve(process.cwd(), outFile);
if (!fs.existsSync(inPath)) {
console.error(`❌ Input CSS not found: ${inPath}`);
process.exit(1);
}
let vars = extractCssVars(inPath, selectors);
if (allowlist instanceof RegExp) {
vars = vars.filter((v) => allowlist.test(v));
}
// remove duplicates, sort
vars = Array.from(new Set(vars)).sort();
const header =
`/* AUTO-GENERATED FILE. DO NOT EDIT.
* Source: ${path.relative(process.cwd(), inPath)}
* Run: npm run gen:css-js
*/
`;
const lines = vars.map((v) => {
const camel = kebabToCamel(v);
return `export const ${camel} = 'var(${v})'`;
});
const content = `${header}\n${lines.join("\n")}\n`;
fs.mkdirSync(path.dirname(outPath), { recursive: true });
fs.writeFileSync(outPath, content, "utf8");
console.log(`✅ Generated ${path.relative(process.cwd(), outPath)} with ${vars.length} vars.`);
}
run().catch((e) => {
console.error(e);
process.exit(1);
});
Example Result:
...
export const spacing720px = 'var(--spacing-720px)'
export const spacing768px = 'var(--spacing-768px)'
export const spacing80px = 'var(--spacing-80px)'
export const spacing8px = 'var(--spacing-8px)'
export const spacing96px = 'var(--spacing-96px)'
export const stroke = 'var(--stroke)'
...
Convert to d.ts
import fs from "node:fs";
import path from "node:path";
import postcss from "postcss";
/** --kebab-case -> camelCase (ลบเครื่องหมาย -- และแปลง -x เป็น X) */
function kebabToCamel(cssVar) {
return cssVar
.replace(/^--/, "")
.replace(/-([a-zA-Z0-9])/g, (_, c) => c.toUpperCase());
}
/**
* อ่านไฟล์ CSS แล้วดึงรายชื่อ CSS custom properties (เริ่มด้วย --)
* เฉพาะกฎ (rule) ที่ selectors match ตามที่กำหนด
*/
function extractCssVarNamesFromFile(filePath, matchSelectors = [":root"]) {
const css = fs.readFileSync(filePath, "utf8");
const root = postcss.parse(css);
/** @type {string[]} */
const names = [];
root.walkRules((rule) => {
const sels = rule.selectors || [];
const matched = sels.some((s) => matchSelectors.includes(s));
if (!matched) return;
rule.walkDecls((decl) => {
if (decl.prop && decl.prop.startsWith("--")) {
names.push(decl.prop);
}
});
});
return names;
}
/** main */
async function run({
input = "styles/tokens.css",
outDts = "src/styles/tokens.d.ts",
selectors = [":root"], // ใส่ selector เพิ่มได้ เช่น '[data-theme="light"]'
allowlist = null, // เช่น /^--body-/ ถ้าอยากกรองเฉพาะ prefix
} = {}) {
const inPath = path.resolve(process.cwd(), input);
const outPath = path.resolve(process.cwd(), outDts);
if (!fs.existsSync(inPath)) {
console.error(`❌ Input CSS not found: ${inPath}`);
process.exit(1);
}
let varNames = extractCssVarNamesFromFile(inPath, selectors);
// กรองด้วย allowlist (ถ้ากำหนด)
if (allowlist instanceof RegExp) {
varNames = varNames.filter((n) => allowlist.test(n));
}
// แปลงเป็น camelCase + unique + sort
const camelNames = Array.from(
new Set(varNames.map(kebabToCamel))
).sort((a, b) => a.localeCompare(b));
// สร้างเนื้อหา .d.ts
const header =
`/* AUTO-GENERATED. DO NOT EDIT.
* Source: ${path.relative(process.cwd(), inPath)}
* Run: npm run gen:css-dts
*/
`;
const lines = camelNames.map((name) => `export const ${name}: string`).join("\n");
const content = `${header}${lines}\n`;
// เขียนไฟล์
fs.mkdirSync(path.dirname(outPath), { recursive: true });
fs.writeFileSync(outPath, content, "utf8");
console.log(`✅ Generated ${path.relative(process.cwd(), outPath)} with ${camelNames.length} exports.`);
}
run().catch((e) => {
console.error(e);
process.exit(1);
});
Example Result
...
export const spacing720px: string
export const spacing768px: string
export const spacing80px: string
export const spacing8px: string
export const spacing96px: string
export const stroke: string
...