@@ -36,6 +36,7 @@ type WithOptionalLayer<T extends Theme> = T & {
3636interface ThemeSubFunctions {
3737 fallbackVar : typeof fallbackVar ;
3838 raw ( varReference : unknown ) : string ;
39+ alias ( varReference : unknown ) : string ;
3940}
4041
4142type ResolveThemeOutput < T extends Theme > = Resolve < ResolveTheme < T > > ;
@@ -125,7 +126,18 @@ function assignTokensWithPrefix<ThemeTokens extends Theme>(
125126 const vars : AssignedVars = { } ;
126127 const resolvedTokens = { } as ResolveTheme < ThemeTokens > ;
127128
128- // Add fallbackVar utility to the context for use in getters
129+ // Execute two-pass token resolution:
130+ // 1. Assign CSS variables to all tokens
131+ // 2. Resolve semantic token references
132+ const context : TokenProcessingContext = {
133+ prefix,
134+ path : [ ] ,
135+ parentPath : prefix ,
136+ cssVarMap : { } ,
137+ aliasMap : new Set ( )
138+ } ;
139+
140+ // Add utilities to the context for use in getters
129141 Object . defineProperty ( resolvedTokens , "fallbackVar" , {
130142 value : fallbackVar ,
131143 enumerable : false ,
@@ -141,21 +153,28 @@ function assignTokensWithPrefix<ThemeTokens extends Theme>(
141153 writable : false
142154 } ) ;
143155
144- // Execute two-pass token resolution:
145- // 1. Assign CSS variables to all tokens
146- // 2. Resolve semantic token references
147- const context : TokenProcessingContext = {
148- prefix,
149- path : [ ] ,
150- parentPath : prefix ,
151- cssVarMap : { }
152- } ;
156+ // Add alias utility to create object references instead of CSS variables
157+ Object . defineProperty ( resolvedTokens , "alias" , {
158+ value : createAliasFunction ( context ) ,
159+ enumerable : false ,
160+ configurable : false ,
161+ writable : false
162+ } ) ;
163+
153164 assignTokenVariables ( tokens , vars , resolvedTokens , context ) ;
154165 resolveSemanticTokens ( tokens , vars , resolvedTokens , context ) ;
155166
156167 return { vars, resolvedTokens } ;
157168}
158169
170+ /**
171+ * Removes hash suffixes from CSS variable names and var() references
172+ */
173+ function stripHash ( str : string ) : string {
174+ // Remove hash suffix from CSS variable names and var() references
175+ return str . replace ( / _ _ [ a - z A - Z 0 - 9 ] + / g, "" ) ;
176+ }
177+
159178// == Two-Pass Token Resolution ===============================================
160179/**
161180 * Context object for token processing functions
@@ -165,6 +184,7 @@ interface TokenProcessingContext {
165184 path : string [ ] ; // Current path in object tree
166185 parentPath : string ; // Current variable path
167186 cssVarMap : CSSVarMap ; // Cache for CSS variable names
187+ aliasMap : Set < string > ; // Track paths that should be aliases (not create CSS vars)
168188}
169189
170190/**
@@ -218,7 +238,8 @@ function assignTokenVariables(
218238 prefix : context . prefix ,
219239 path : currentPath ,
220240 parentPath : varPath ,
221- cssVarMap : context . cssVarMap
241+ cssVarMap : context . cssVarMap ,
242+ aliasMap : context . aliasMap
222243 }
223244 ) ;
224245 } else {
@@ -280,7 +301,8 @@ function assignTokenVariables(
280301 prefix : context . prefix ,
281302 path : currentPath ,
282303 parentPath : varPath ,
283- cssVarMap : context . cssVarMap
304+ cssVarMap : context . cssVarMap ,
305+ aliasMap : context . aliasMap
284306 } ) ;
285307 continue ;
286308 }
@@ -313,17 +335,28 @@ function resolveSemanticTokens(
313335
314336 // Handle getters (semantic tokens)
315337 if ( typeof descriptor . get === "function" ) {
316- const cssVar = getCSSVarByPath ( varPath , context ) ;
317-
338+ // Set the current path for alias() to access
339+ currentProcessingPath = currentPath ;
318340 // Call getter with resolvedTokens as this context
319341 // This allows semantic tokens to reference other tokens
320342 const computedValue = descriptor . get . call ( resolvedTokens ) ;
343+ // Clear the current path after getter execution
344+ currentProcessingPath = [ ] ;
321345
322- // Store the computed reference (should be a var() reference)
323- vars [ cssVar ] = computedValue ;
346+ // Check if this path is marked as an alias
347+ const currentPathStr = currentPath . join ( "." ) ;
324348
325- // Update resolvedTokens with the actual reference
326- setByPath ( resolvedTokens , currentPath , computedValue ) ;
349+ if ( context . aliasMap . has ( currentPathStr ) ) {
350+ // Don't create a CSS variable for aliases
351+ // Just update resolvedTokens with the aliased reference (unhashed)
352+ const unhashedValue = stripHash ( computedValue ) ;
353+ setByPath ( resolvedTokens , currentPath , unhashedValue ) ;
354+ } else {
355+ // Normal flow: create CSS variable
356+ const cssVar = getCSSVarByPath ( varPath , context ) ;
357+ vars [ cssVar ] = computedValue ;
358+ setByPath ( resolvedTokens , currentPath , computedValue ) ;
359+ }
327360 continue ;
328361 }
329362
@@ -343,7 +376,8 @@ function resolveSemanticTokens(
343376 prefix : context . prefix ,
344377 path : currentPath ,
345378 parentPath : varPath ,
346- cssVarMap : context . cssVarMap
379+ cssVarMap : context . cssVarMap ,
380+ aliasMap : context . aliasMap
347381 }
348382 ) ;
349383 continue ;
@@ -355,7 +389,8 @@ function resolveSemanticTokens(
355389 prefix : context . prefix ,
356390 path : currentPath ,
357391 parentPath : varPath ,
358- cssVarMap : context . cssVarMap
392+ cssVarMap : context . cssVarMap ,
393+ aliasMap : context . aliasMap
359394 } ) ;
360395 }
361396 }
@@ -503,6 +538,24 @@ function createRawExtractor(vars: AssignedVars) {
503538 } ;
504539}
505540
541+ /**
542+ * Creates a function that marks tokens as aliases.
543+ * Aliased tokens don't create their own CSS variables but reference existing ones.
544+ */
545+ // Store the current processing path for alias() to access
546+ let currentProcessingPath : string [ ] = [ ] ;
547+
548+ function createAliasFunction ( context : TokenProcessingContext ) {
549+ return function alias ( varReference : unknown ) : string {
550+ // Mark the current processing path as an alias
551+ const currentPathStr = currentProcessingPath . join ( "." ) ;
552+
553+ context . aliasMap . add ( currentPathStr ) ;
554+ // Return the var reference unchanged
555+ return String ( varReference ) ;
556+ } ;
557+ }
558+
506559// == Token Value Extractors ==================================================
507560function extractFontFamilyValue ( value : TokenFontFamilyValue ) : CSSVarValue {
508561 if ( Array . isArray ( value ) ) {
@@ -652,10 +705,6 @@ if (import.meta.vitest) {
652705 }
653706
654707 // Test utility functions for handling hashed CSS variables
655- function stripHash ( str : string ) : string {
656- // Remove hash suffix from CSS variable names and var() references
657- return str . replace ( / _ _ [ a - z A - Z 0 - 9 ] + / g, "" ) ;
658- }
659708
660709 function normalizeVars ( vars : AssignedVars ) : AssignedVars {
661710 const normalized : AssignedVars = { } ;
@@ -894,6 +943,62 @@ if (import.meta.vitest) {
894943 ) ;
895944 } ) ;
896945
946+ it ( "handles alias references in semantic tokens" , ( ) => {
947+ const result = assignTokens (
948+ composedValue ( {
949+ color : {
950+ base : {
951+ blue : "#0000ff" ,
952+ red : "#ff0000"
953+ } ,
954+ semantic : {
955+ get primary ( ) : string {
956+ // Use alias to reference without creating CSS var
957+ return this . alias ( this . color . base . blue ) ;
958+ } ,
959+ get danger ( ) : string {
960+ // Another alias
961+ return this . alias ( this . color . base . red ) ;
962+ } ,
963+ get secondary ( ) : string {
964+ // Normal reference (creates CSS var)
965+ return this . color . base . blue ;
966+ }
967+ }
968+ }
969+ } )
970+ ) ;
971+
972+ validateHashFormat ( result . vars ) ;
973+
974+ const normalizedVars = normalizeVars ( result . vars ) ;
975+ // Base tokens should have CSS variables
976+ expect ( normalizedVars [ "--color-base-blue" ] ) . toBe ( "#0000ff" ) ;
977+ expect ( normalizedVars [ "--color-base-red" ] ) . toBe ( "#ff0000" ) ;
978+
979+ // Aliased semantic tokens should NOT have CSS variables
980+
981+ expect ( normalizedVars [ "--color-semantic-primary" ] ) . toBeUndefined ( ) ;
982+ expect ( normalizedVars [ "--color-semantic-danger" ] ) . toBeUndefined ( ) ;
983+
984+ // Non-aliased semantic token should have CSS variable
985+ expect ( stripHash ( normalizedVars [ "--color-semantic-secondary" ] ) ) . toBe (
986+ "var(--color-base-blue)"
987+ ) ;
988+
989+ // Resolved tokens should all use var() references
990+ const normalizedTokens = normalizeResolvedTokens ( result . resolvedTokens ) ;
991+ expect ( normalizedTokens . color . semantic . primary ) . toBe (
992+ "var(--color-base-blue)"
993+ ) ;
994+ expect ( normalizedTokens . color . semantic . danger ) . toBe (
995+ "var(--color-base-red)"
996+ ) ;
997+ expect ( normalizedTokens . color . semantic . secondary ) . toBe (
998+ "var(--color-base-blue)"
999+ ) ;
1000+ } ) ;
1001+
8971002 it ( "handles raw value extraction in semantic tokens" , ( ) => {
8981003 const result = assignTokens (
8991004 composedValue ( {
0 commit comments