The static side of a class consists of the class constructor, and there is exactly one such constructor. It has no access to any generic parameters on the instance type, because a class constructor needs to be able to create instances for any possible specification of that generic parameter. In your ThemeExample<T> code, the class constructor must be able to make instances of, say, ThemeExample<string> and ThemeExample<number>. If you could have a static property of type T, it would mean that it would have to be both a string and a number and any other possible type for T. It doesn't work.
Conceptually you could imagine ThemeExample as having a static property named theme of type unknown, and then when you call ThemeExample.init("hello"), the compiler would narrow the ThemeExample class constructor so that theme is now of type string instead of unknown. But there is no good mechanism to do this. You could imagine writing something like an assertion function which would have such an effect:
interface ThemeExampleConstructor<T = unknown> {
theme: T;
init<T>(theme: T): asserts this is ThemeExampleConstructor<T>;
new(): ThemeExample<T>;
}
declare const ThemeExample: ThemeExampleConstructor;
ThemeExample.theme.toUpperCase(); // error
ThemeExample.init("hello");
ThemeExample.theme.toUpperCase(); // okay
new ThemeExample().instanceProp.toUpperCase(); // okay
But you can't do it with class syntax, and even if you got this done, narrowing like this doesn't work across file boundaries or even function boundaries. There's no mechanism whereby the compiler would see that ThemeExample.init("hello") in one file happens before the ThemeExample.theme.toUpperCase() in another file. So we can't do this at all.
Instead I'd suggest going the route of a factory function which produces new class constructors. This function would exist in some library, say Library/index.ts:
export function initThemeExample<T>(theme: T) {
return class ThemeExample {
static theme = theme;
};
}
Then in another file like userLib.ts, a user could import that from the library, configure, and re-export:
import {initThemeExample} from './Library'
//config it
export const ThemeExample = initThemeExample({
hello: 'hi'
})
And then the user could use this configured constructor in all other files:
import {ThemeExample} from './userLib'
//import your ThemeExaple in another file
document.write(ThemeExample.theme.hello.toUpperCase()); // HI
Link to StackBlitz code
const"configured version" and import it in different files. If that meets your needs I can write up an answer; otherwise I can only imagine a "this is impossible, sorry" answer.