Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add style expression support #1058

Merged
merged 7 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.3.832
0.3.833
219 changes: 214 additions & 5 deletions docs/docs/03-syntax-and-usage/11-css-style-management.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,230 @@
# CSS style management

## HTML class attribute
## HTML class and style attributes

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

```templ
templ button(text string) {
<button class="button is-primary">{ text }</button>
<button class="button is-primary" style="background-color: red">{ text }</button>
}
```

```html title="Output"
<button class="button is-primary">
<button class="button is-primary" style="background-color: red">
Click me
</button>
```

## Class expression
## Style attribute

To use a variable in the style attribute, use braces to denote the Go expression.

```templ
templ button(style, text string) {
<button style={ style }>{ text }</button>
}
```

You can pass multiple values to the `style` attribute. The results are alll added to the output.
a-h marked this conversation as resolved.
Show resolved Hide resolved

```templ
templ button(style1, style2 string, text string) {
<button style={ style1, style2 }>{ text }</button>
}
```

The style attribute supports use of the following types:

* `string` - A string containing CSS properties, e.g. `background-color: red`.
* `templ.SafeCSS` - A value containing CSS properties and values that will not be sanitized, e.g. `background-color: red; text-decoration: underline`
* `map[string]string` - A map of string keys to string values, e.g. `map[string]string{"color": "red"}`
* `map[string]templ.SafeCSSProperty` - A map of string keys to values, where the values will not be sanitized.
* `templ.KeyValue[string, string]` - A single CSS key/value.
* `templ.KeyValue[string, templ.SafeCSSProperty` - A CSS key/value, but the value will not be sanitized.
* `templ.KeyValue[string, bool]` - A map where the CSS in the key is onlyl included in the output if the boolean value is true.
a-h marked this conversation as resolved.
Show resolved Hide resolved
* `templ.KeyValue[templ.SafeCSS, bool]` - A map where the CSS in the key is only included if the boolean value is true.

Finally, a function value that returns any of the above types can be used.

Go syntax allows you to pass a single function that returns a value and an error.

```templ
templ Page(userType string) {
<div style={ getStyle(userType) }>Styled</div>
}

func getStyle(userType string) (string, error) {
//TODO: Look up in something that might error.
return "background-color: red", errors.New("failed")
}
```

Or multiple functions and values that return a single type.

```templ
templ Page(userType string) {
<div style={ getStyle(userType), "color: blue" }>Styled</div>
}

func getStyle(userType string) (string) {
return "background-color: red"
}
```

### Style attribute examples

#### Maps

Maps are useful when styles need to be dynamically computed based on component state or external inputs.

```templ
func getProgressStyle(percent int) map[string]string {
return map[string]string{
"width": fmt.Sprintf("%d%%", percent),
"transition": "width 0.3s ease",
}
}

templ ProgressBar(percent int) {
<div style={ getProgressStyle(percent) } class="progress-bar">
<div class="progress-fill"></div>
</div>
}
```

```html title="Output (percent=75)"
<div style="transition:width 0.3s ease;width:75%;" class="progress-bar">
<div class="progress-fill"></div>
</div>
```

#### KeyValue pattern

The `templ.KV` helper provides conditional style application in a more compact syntax.

```templ
templ TextInput(value string, hasError bool) {
<input
type="text"
value={ value }
style={
templ.KV("border-color: #ff3860", hasError),
templ.KV("background-color: #fff5f7", hasError),
"padding: 0.5em 1em;",
}
>
}
```

```html title="Output (hasError=true)"
<input
type="text"
value=""
style="border-color: #ff3860; background-color: #fff5f7; padding: 0.5em 1em;">
```

#### Bypassing sanitization

By default, dynamic CSS values are sanitized to protect against dangerous CSS values that might introduce vulnerabilities into your application.

However, if you're sure, you can bypass sanitization by marking your content as safe with the `templ.SafeCSS` and `templ.SafeCSSProperty` types.

```templ
func calculatePositionStyles(x, y int) templ.SafeCSS {
return templ.SafeCSS(fmt.Sprintf(
"transform: translate(%dpx, %dpx);",
x*2, // Example calculation
y*2,
))
}

templ DraggableElement(x, y int) {
<div style={ calculatePositionStyles(x, y) }>
Drag me
</div>
}
```

```html title="Output (x=10, y=20)"
<div style="transform: translate(20px, 40px);">
Drag me
</div>
```

### Pattern use cases

| Pattern | Best For | Example Use Case |
|---------|----------|------------------|
| **Maps** | Dynamic style sets requiring multiple computed values | Progress indicators, theme switching |
| **KeyValue** | Conditional style toggling | Form validation, interactive states |
| **Functions** | Complex style generation | Animations, data visualizations |
| **Direct Strings** | Simple static styles | Basic formatting, utility classes |

### Sanitization behaviour

By default, dynamic CSS values are sanitized to protect against dangerous CSS values that might introduce vulnerabilities into your application.

```templ
templ UnsafeExample() {
<div style={ "background-image: url('javascript:alert(1)')" }>
Dangerous content
</div>
}
```

```html title="Output"
<div style="background-image:zTemplUnsafeCSSPropertyValue;">
Dangerous content
</div>
```

These protections can be bypassed with the `templ.SafeCSS` and `templ.SafeCSSProperty` types.

```templ
templ SafeEmbed() {
<div style={ templ.SafeCSS("background-image: url(/safe.png);") }>
Trusted content
</div>
}
```

```html title="Output"
<div style="background-image: url(/safe.png);">
Trusted content
</div>
```

:::note
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.
:::

### Error Handling

Invalid values are automatically sanitized:

```templ
templ InvalidButton() {
<button style={
map[string]string{
"": "invalid-property",
"color": "</style>",
}
}>Click me</button>
}
```

```html title="Output"
<button style="zTemplUnsafeCSSPropertyName:zTemplUnsafeCSSPropertyValue;color:zTemplUnsafeCSSPropertyValue;">
Click me
</button>
```

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.

As such, the attribute takes `any`, and executes type checks at runtime. Any invalid types will produce the CSS value `zTemplUnsupportedStyleAttributeValue:Invalid;`.

## Class attributes

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

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

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

* A `string` containing the name of a class to apply.
* 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.
* `templ.KV("is-primary", true)`
* `templ.KV("hover:red", true)`
Expand Down
Loading
Loading