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

refactor: remove class name sanitization, since CSS class names are already HTML attribute escaped #284

Merged
merged 5 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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.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