Skip to content

Commit

Permalink
feat: remove class name sanitization, since CSS class names are alrea…
Browse files Browse the repository at this point in the history
…dy HTML attribute escaped (#284)

Fixes #211
  • Loading branch information
a-h authored Nov 11, 2023
1 parent fb53582 commit 7a905f8
Show file tree
Hide file tree
Showing 9 changed files with 315 additions and 271 deletions.
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.461
0.2.455
28 changes: 8 additions & 20 deletions docs/docs/03-syntax-and-usage/10-css-style-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## HTML class attribute

templ supports the HTML `class` attribute.
The standard HTML `class` attribute can be added to components to set class names.

```templ
templ button(text string) {
Expand Down Expand Up @@ -38,38 +38,26 @@ templ button(text string, className string) {
}
```

## CSS class name sanitization

CSS class names that are passed to the class expression attribute as variables are sanitized, since the Go string might come from an untrustworthy source such as user input.

If the class name fails the sanitization check, it will be replaced with `--templ-css-class-safe-name`.

If you know that the CSS class name is from a trustworthy source (e.g. a string constant under your control), you can bypass sanitization by marking the class name as safe with the `templ.SafeClass()` function.

```templ title="component.templ"
package main
templ button(text string) {
<button class={ "button", templ.SafeClass("hover:do_not_sanitize") }>{ text }</button>
}
```

### Dynamic class names

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

* 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(templ.SafeClass("hover:do_not_sanitize"), true)`
* `templ.KV("hover:red", true)`
* A map of string class names to a boolean that determines if the class is added to the class attribute value at render time:
* `map[string]bool`
* `map[CSSClass]bool`
* `map[CSSClass]bool`

```templ title="component.templ"
package main
css red() {
background-color: #ff0000;
}
templ button(text string, isPrimary bool) {
<button class={ "button", templ.KV("is-primary", isPrimary), templ.KV(templ.SafeClass("hover:do_not_sanitize", isPrimary) }>{ text }</button>
<button class={ "button", templ.KV("is-primary", isPrimary), templ.KV(red(), isPrimary) }>{ text }</button>
}
```

Expand Down
3 changes: 0 additions & 3 deletions generator/test-css-usage/constants.go

This file was deleted.

31 changes: 15 additions & 16 deletions generator/test-css-usage/expected.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
<style>
.test {
color: #ff0000;
}
</style>
<style type="text/css">
.className_f179{background-color:#ffffff;color:#ff0000;}
</style>
<button class="className_f179 --templ-css-class-safe-name safe safe2" type="button">A</button>
<button class="className_f179 --templ-css-class-safe-name safe safe2" type="button">B</button>
<style type="text/css">.green_58d2{color:#00ff00;}</style>
<button class="green_58d2" type="button">Green</button>
<div class="a c"></div>
<div class="a"></div>
.test {
color: #ff0000;
}
</style>
<div class="test">Style tags are supported</div>
<style type="text/css">.cssComponentGreen_58d2{color:#00ff00;}</style>
<div class="cssComponentGreen_58d2">CSS components are supported</div>
<div class="cssComponentGreen_58d2 classA &amp;&amp;&amp;classB classC d e" type="button">Both CSS components and constants are supported</div>
<div class="cssComponentGreen_58d2 classA &amp;&amp;&amp;classB classC d e" type="button">Both CSS components and constants are supported</div>
<div class="a c">Maps can be used to determine if a class should be added or not.</div>
<style type="text/css">.e_739d{font-size:14pt;}</style>
<input type="email" id="email" name="email" class="a b e_739d" placeholder="[email protected]" autocomplete="off"/>
<button class="bg-violet-500 hover:bg-violet-600">Save changes</button>

<div class="a c e_739d">KV can be used to conditionally set classes.</div>
<div class="bg-violet-500 hover:bg-red-600 hover:bg-sky-700 text-[#50d71e] w-[calc(100%-4rem)">Psuedo attributes and complex class names are supported.</div>
<div class="a&#34; onClick=&#34;alert(&#39;hello&#39;)&#34;">
Class names are HTML escaped.
</div>
2 changes: 1 addition & 1 deletion generator/test-css-usage/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
var expected string

func Test(t *testing.T) {
component := ThreeButtons()
component := TestComponent()

diff, err := htmldiff.Diff(component, expected)
if err != nil {
Expand Down
82 changes: 53 additions & 29 deletions generator/test-css-usage/template.templ
Original file line number Diff line number Diff line change
@@ -1,14 +1,45 @@
package testcssusage

css green() {
color: #00ff00;
// Constant class.

templ StyleTagsAreSupported() {
<style>
.test {
color: #ff0000;
}
</style>
<div class="test">Style tags are supported</div>
}

css className() {
background-color: #ffffff;
// CSS components.

const red = "#00ff00"

css cssComponentGreen() {
color: { red };
}

templ CSSComponentsAreSupported() {
<div class={ cssComponentGreen() }>CSS components are supported</div>
}

// Both CSS components and constants are supported.
// Only string names are really required. There is no need to use templ.Class or templ.SafeClass.

templ CSSComponentsAndConstantsAreSupported() {
<div class={ cssComponentGreen(), "classA", templ.Class("&&&classB"), templ.SafeClass("classC"), "d e" } type="button">Both CSS components and constants are supported</div>
// The following is also valid, but not required - you can put the class names in directly.
<div class={ templ.Classes(cssComponentGreen(), "classA", templ.Class("&&&classB"), templ.SafeClass("classC")), "d e" } type="button">Both CSS components and constants are supported</div>
}

// Maps can be used to determine if a class should be added or not.

templ MapsCanBeUsedToConditionallySetClasses() {
<div class={ map[string]bool{"a": true, "b": false, "c": true} }>Maps can be used to determine if a class should be added or not.</div>
}

// The templ.KV function can be used to add a class if a condition is true.

css d() {
font-size: 12pt;
}
Expand All @@ -17,37 +48,30 @@ css e() {
font-size: 14pt;
}

templ Button(text string) {
<button class={ className(), templ.Class("&&&unsafe"), "safe", templ.SafeClass("safe2") } type="button">{ text }</button>
templ KVCanBeUsedToConditionallySetClasses() {
<div class={ "a", templ.KV("b", false), "c", templ.KV(d(), false), templ.KV(e(), true) }>KV can be used to conditionally set classes.</div>
}

templ LegacySupport() {
<div class={ templ.Classes(templ.Class("test"), "a") }></div>
}
// Pseudo attributes can be used without any special syntax.

templ MapCSSExample() {
<div class={ map[string]bool{"a": true, "b": false, "c": true} }></div>
templ PsuedoAttributesAndComplexClassNamesAreSupported() {
<div class={ "bg-violet-500", "hover:bg-red-600", "hover:bg-sky-700", "text-[#50d71e]", "w-[calc(100%-4rem)" }>Psuedo attributes and complex class names are supported.</div>
}

templ KVExample() {
<div class={ "a", templ.KV("b", false) }></div>
<input type="email" id="email" name="email" class={ "a", "b", "c", templ.KV("c", false), templ.KV(d(), false), templ.KV(e(), true) } placeholder="[email protected]" autocomplete="off"/>
}
// Class names are HTML escaped.

templ PsuedoAttributes() {
<button class={ "bg-violet-500", templ.KV(templ.SafeClass("hover:bg-violet-600"), true) }>Save changes</button>
templ ClassNamesAreHTMLEscaped() {
<div class={ "a\" onClick=\"alert('hello')\"" }>Class names are HTML escaped.</div>
}

templ ThreeButtons() {
<style>
.test {
color: #ff0000;
}
</style>
{! Button("A") }
{! Button("B") }
<button class={ templ.Classes(green) } type="button">{ "Green" }</button>
{! MapCSSExample() }
{! KVExample() }
{! PsuedoAttributes() }
// Combine all tests.

templ TestComponent() {
@StyleTagsAreSupported()
@CSSComponentsAreSupported()
@CSSComponentsAndConstantsAreSupported()
@MapsCanBeUsedToConditionallySetClasses()
@KVCanBeUsedToConditionallySetClasses()
@PsuedoAttributesAndComplexClassNamesAreSupported()
@ClassNamesAreHTMLEscaped()
}
Loading

0 comments on commit 7a905f8

Please sign in to comment.