Skip to content

Commit fea7372

Browse files
a-hjoerdav
andauthored
feat: add style expression support (#1058)
Co-authored-by: Joe Davidson <joe.davidson.21111@gmail.com>
1 parent 0474dd9 commit fea7372

File tree

12 files changed

+1084
-104
lines changed

12 files changed

+1084
-104
lines changed

.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.3.832
1+
0.3.833

docs/docs/03-syntax-and-usage/11-css-style-management.md

Lines changed: 214 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,230 @@
11
# CSS style management
22

3-
## HTML class attribute
3+
## HTML class and style attributes
44

5-
The standard HTML `class` attribute can be added to components to set class names.
5+
The standard HTML `class` and `style` attributes can be added to components. Note the use of standard quotes to denote a static value.
66

77
```templ
88
templ button(text string) {
9-
<button class="button is-primary">{ text }</button>
9+
<button class="button is-primary" style="background-color: red">{ text }</button>
1010
}
1111
```
1212

1313
```html title="Output"
14-
<button class="button is-primary">
14+
<button class="button is-primary" style="background-color: red">
1515
Click me
1616
</button>
1717
```
1818

19-
## Class expression
19+
## Style attribute
20+
21+
To use a variable in the style attribute, use braces to denote the Go expression.
22+
23+
```templ
24+
templ button(style, text string) {
25+
<button style={ style }>{ text }</button>
26+
}
27+
```
28+
29+
You can pass multiple values to the `style` attribute. The results are all added to the output.
30+
31+
```templ
32+
templ button(style1, style2 string, text string) {
33+
<button style={ style1, style2 }>{ text }</button>
34+
}
35+
```
36+
37+
The style attribute supports use of the following types:
38+
39+
* `string` - A string containing CSS properties, e.g. `background-color: red`.
40+
* `templ.SafeCSS` - A value containing CSS properties and values that will not be sanitized, e.g. `background-color: red; text-decoration: underline`
41+
* `map[string]string` - A map of string keys to string values, e.g. `map[string]string{"color": "red"}`
42+
* `map[string]templ.SafeCSSProperty` - A map of string keys to values, where the values will not be sanitized.
43+
* `templ.KeyValue[string, string]` - A single CSS key/value.
44+
* `templ.KeyValue[string, templ.SafeCSSProperty` - A CSS key/value, but the value will not be sanitized.
45+
* `templ.KeyValue[string, bool]` - A map where the CSS in the key is only included in the output if the boolean value is true.
46+
* `templ.KeyValue[templ.SafeCSS, bool]` - A map where the CSS in the key is only included if the boolean value is true.
47+
48+
Finally, a function value that returns any of the above types can be used.
49+
50+
Go syntax allows you to pass a single function that returns a value and an error.
51+
52+
```templ
53+
templ Page(userType string) {
54+
<div style={ getStyle(userType) }>Styled</div>
55+
}
56+
57+
func getStyle(userType string) (string, error) {
58+
//TODO: Look up in something that might error.
59+
return "background-color: red", errors.New("failed")
60+
}
61+
```
62+
63+
Or multiple functions and values that return a single type.
64+
65+
```templ
66+
templ Page(userType string) {
67+
<div style={ getStyle(userType), "color: blue" }>Styled</div>
68+
}
69+
70+
func getStyle(userType string) (string) {
71+
return "background-color: red"
72+
}
73+
```
74+
75+
### Style attribute examples
76+
77+
#### Maps
78+
79+
Maps are useful when styles need to be dynamically computed based on component state or external inputs.
80+
81+
```templ
82+
func getProgressStyle(percent int) map[string]string {
83+
return map[string]string{
84+
"width": fmt.Sprintf("%d%%", percent),
85+
"transition": "width 0.3s ease",
86+
}
87+
}
88+
89+
templ ProgressBar(percent int) {
90+
<div style={ getProgressStyle(percent) } class="progress-bar">
91+
<div class="progress-fill"></div>
92+
</div>
93+
}
94+
```
95+
96+
```html title="Output (percent=75)"
97+
<div style="transition:width 0.3s ease;width:75%;" class="progress-bar">
98+
<div class="progress-fill"></div>
99+
</div>
100+
```
101+
102+
#### KeyValue pattern
103+
104+
The `templ.KV` helper provides conditional style application in a more compact syntax.
105+
106+
```templ
107+
templ TextInput(value string, hasError bool) {
108+
<input
109+
type="text"
110+
value={ value }
111+
style={
112+
templ.KV("border-color: #ff3860", hasError),
113+
templ.KV("background-color: #fff5f7", hasError),
114+
"padding: 0.5em 1em;",
115+
}
116+
>
117+
}
118+
```
119+
120+
```html title="Output (hasError=true)"
121+
<input
122+
type="text"
123+
value=""
124+
style="border-color: #ff3860; background-color: #fff5f7; padding: 0.5em 1em;">
125+
```
126+
127+
#### Bypassing sanitization
128+
129+
By default, dynamic CSS values are sanitized to protect against dangerous CSS values that might introduce vulnerabilities into your application.
130+
131+
However, if you're sure, you can bypass sanitization by marking your content as safe with the `templ.SafeCSS` and `templ.SafeCSSProperty` types.
132+
133+
```templ
134+
func calculatePositionStyles(x, y int) templ.SafeCSS {
135+
return templ.SafeCSS(fmt.Sprintf(
136+
"transform: translate(%dpx, %dpx);",
137+
x*2, // Example calculation
138+
y*2,
139+
))
140+
}
141+
142+
templ DraggableElement(x, y int) {
143+
<div style={ calculatePositionStyles(x, y) }>
144+
Drag me
145+
</div>
146+
}
147+
```
148+
149+
```html title="Output (x=10, y=20)"
150+
<div style="transform: translate(20px, 40px);">
151+
Drag me
152+
</div>
153+
```
154+
155+
### Pattern use cases
156+
157+
| Pattern | Best For | Example Use Case |
158+
|---------|----------|------------------|
159+
| **Maps** | Dynamic style sets requiring multiple computed values | Progress indicators, theme switching |
160+
| **KeyValue** | Conditional style toggling | Form validation, interactive states |
161+
| **Functions** | Complex style generation | Animations, data visualizations |
162+
| **Direct Strings** | Simple static styles | Basic formatting, utility classes |
163+
164+
### Sanitization behaviour
165+
166+
By default, dynamic CSS values are sanitized to protect against dangerous CSS values that might introduce vulnerabilities into your application.
167+
168+
```templ
169+
templ UnsafeExample() {
170+
<div style={ "background-image: url('javascript:alert(1)')" }>
171+
Dangerous content
172+
</div>
173+
}
174+
```
175+
176+
```html title="Output"
177+
<div style="background-image:zTemplUnsafeCSSPropertyValue;">
178+
Dangerous content
179+
</div>
180+
```
181+
182+
These protections can be bypassed with the `templ.SafeCSS` and `templ.SafeCSSProperty` types.
183+
184+
```templ
185+
templ SafeEmbed() {
186+
<div style={ templ.SafeCSS("background-image: url(/safe.png);") }>
187+
Trusted content
188+
</div>
189+
}
190+
```
191+
192+
```html title="Output"
193+
<div style="background-image: url(/safe.png);">
194+
Trusted content
195+
</div>
196+
```
197+
198+
:::note
199+
HTML attribute escaping is not bypassed, so `<`, `>`, `&` and quotes will always appear as HTML entities (`&lt;` etc.) in attributes - this is good practice, and doesn't affect how browsers use the CSS.
200+
:::
201+
202+
### Error Handling
203+
204+
Invalid values are automatically sanitized:
205+
206+
```templ
207+
templ InvalidButton() {
208+
<button style={
209+
map[string]string{
210+
"": "invalid-property",
211+
"color": "</style>",
212+
}
213+
}>Click me</button>
214+
}
215+
```
216+
217+
```html title="Output"
218+
<button style="zTemplUnsafeCSSPropertyName:zTemplUnsafeCSSPropertyValue;color:zTemplUnsafeCSSPropertyValue;">
219+
Click me
220+
</button>
221+
```
222+
223+
Go's type system doesn't support union types, so it's not possible to limit the inputs to the style attribute to just the supported types.
224+
225+
As such, the attribute takes `any`, and executes type checks at runtime. Any invalid types will produce the CSS value `zTemplUnsupportedStyleAttributeValue:Invalid;`.
226+
227+
## Class attributes
20228

21229
To use a variable as the name of a CSS class, use a CSS expression.
22230

@@ -42,6 +250,7 @@ templ button(text string, className string) {
42250

43251
Toggle addition of CSS classes to an element based on a boolean value by passing:
44252

253+
* A `string` containing the name of a class to apply.
45254
* A `templ.KV` value containing the name of the class to add to the element, and a boolean that determines whether the class is added to the attribute at render time.
46255
* `templ.KV("is-primary", true)`
47256
* `templ.KV("hover:red", true)`

0 commit comments

Comments
 (0)