diff --git a/.version b/.version
index 36cfcfb57..2dbed2514 100644
--- a/.version
+++ b/.version
@@ -1 +1 @@
-0.2.461
\ No newline at end of file
+0.2.455
\ No newline at end of file
diff --git a/docs/docs/03-syntax-and-usage/10-css-style-management.md b/docs/docs/03-syntax-and-usage/10-css-style-management.md
index cf83b128c..9241bafc0 100644
--- a/docs/docs/03-syntax-and-usage/10-css-style-management.md
+++ b/docs/docs/03-syntax-and-usage/10-css-style-management.md
@@ -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) {
@@ -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) {
- { text }
-}
-```
-
### 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) {
- { text }
+ { text }
}
```
diff --git a/generator/test-css-usage/constants.go b/generator/test-css-usage/constants.go
deleted file mode 100644
index 2e8a288dc..000000000
--- a/generator/test-css-usage/constants.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package testcssusage
-
-const red = "#ff0000"
diff --git a/generator/test-css-usage/expected.html b/generator/test-css-usage/expected.html
index 2cb71a4a2..db8ab3b81 100644
--- a/generator/test-css-usage/expected.html
+++ b/generator/test-css-usage/expected.html
@@ -1,18 +1,17 @@
-
-A
-B
-
-Green
-
-
+ .test {
+ color: #ff0000;
+ }
+
+Style tags are supported
+
+CSS components are supported
+Both CSS components and constants are supported
+Both CSS components and constants are supported
+Maps can be used to determine if a class should be added or not.
-
-Save changes
-
+KV can be used to conditionally set classes.
+Psuedo attributes and complex class names are supported.
+
+ Class names are HTML escaped.
+
diff --git a/generator/test-css-usage/render_test.go b/generator/test-css-usage/render_test.go
index 9796a31ff..342f692ad 100644
--- a/generator/test-css-usage/render_test.go
+++ b/generator/test-css-usage/render_test.go
@@ -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 {
diff --git a/generator/test-css-usage/template.templ b/generator/test-css-usage/template.templ
index 7bc413ab5..a71d0bfd0 100644
--- a/generator/test-css-usage/template.templ
+++ b/generator/test-css-usage/template.templ
@@ -1,14 +1,45 @@
package testcssusage
-css green() {
- color: #00ff00;
+// Constant class.
+
+templ StyleTagsAreSupported() {
+
+ Style tags are supported
}
-css className() {
- background-color: #ffffff;
+// CSS components.
+
+const red = "#00ff00"
+
+css cssComponentGreen() {
color: { red };
}
+templ CSSComponentsAreSupported() {
+ CSS components are supported
+}
+
+// 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() {
+ Both CSS components and constants are supported
+ // The following is also valid, but not required - you can put the class names in directly.
+ Both CSS components and constants are supported
+}
+
+// Maps can be used to determine if a class should be added or not.
+
+templ MapsCanBeUsedToConditionallySetClasses() {
+ Maps can be used to determine if a class should be added or not.
+}
+
+// The templ.KV function can be used to add a class if a condition is true.
+
css d() {
font-size: 12pt;
}
@@ -17,37 +48,30 @@ css e() {
font-size: 14pt;
}
-templ Button(text string) {
- { text }
+templ KVCanBeUsedToConditionallySetClasses() {
+ KV can be used to conditionally set classes.
}
-templ LegacySupport() {
-
-}
+// Pseudo attributes can be used without any special syntax.
-templ MapCSSExample() {
-
+templ PsuedoAttributesAndComplexClassNamesAreSupported() {
+ Psuedo attributes and complex class names are supported.
}
-templ KVExample() {
-
-
-}
+// Class names are HTML escaped.
-templ PsuedoAttributes() {
- Save changes
+templ ClassNamesAreHTMLEscaped() {
+ Class names are HTML escaped.
}
-templ ThreeButtons() {
-
- {! Button("A") }
- {! Button("B") }
- { "Green" }
- {! MapCSSExample() }
- {! KVExample() }
- {! PsuedoAttributes() }
+// Combine all tests.
+
+templ TestComponent() {
+ @StyleTagsAreSupported()
+ @CSSComponentsAreSupported()
+ @CSSComponentsAndConstantsAreSupported()
+ @MapsCanBeUsedToConditionallySetClasses()
+ @KVCanBeUsedToConditionallySetClasses()
+ @PsuedoAttributesAndComplexClassNamesAreSupported()
+ @ClassNamesAreHTMLEscaped()
}
diff --git a/generator/test-css-usage/template_templ.go b/generator/test-css-usage/template_templ.go
index c7bc89817..ded80499d 100644
--- a/generator/test-css-usage/template_templ.go
+++ b/generator/test-css-usage/template_templ.go
@@ -10,48 +10,9 @@ import "io"
import "bytes"
import "strings"
-func green() templ.CSSClass {
- var templ_7745c5c3_CSSBuilder strings.Builder
- templ_7745c5c3_CSSBuilder.WriteString(`color:#00ff00;`)
- templ_7745c5c3_CSSID := templ.CSSID(`green`, templ_7745c5c3_CSSBuilder.String())
- return templ.ComponentCSSClass{
- ID: templ_7745c5c3_CSSID,
- Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
- }
-}
-
-func className() templ.CSSClass {
- var templ_7745c5c3_CSSBuilder strings.Builder
- templ_7745c5c3_CSSBuilder.WriteString(`background-color:#ffffff;`)
- templ_7745c5c3_CSSBuilder.WriteString(string(templ.SanitizeCSS(`color`, red)))
- templ_7745c5c3_CSSID := templ.CSSID(`className`, templ_7745c5c3_CSSBuilder.String())
- return templ.ComponentCSSClass{
- ID: templ_7745c5c3_CSSID,
- Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
- }
-}
-
-func d() templ.CSSClass {
- var templ_7745c5c3_CSSBuilder strings.Builder
- templ_7745c5c3_CSSBuilder.WriteString(`font-size:12pt;`)
- templ_7745c5c3_CSSID := templ.CSSID(`d`, templ_7745c5c3_CSSBuilder.String())
- return templ.ComponentCSSClass{
- ID: templ_7745c5c3_CSSID,
- Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
- }
-}
-
-func e() templ.CSSClass {
- var templ_7745c5c3_CSSBuilder strings.Builder
- templ_7745c5c3_CSSBuilder.WriteString(`font-size:14pt;`)
- templ_7745c5c3_CSSID := templ.CSSID(`e`, templ_7745c5c3_CSSBuilder.String())
- return templ.ComponentCSSClass{
- ID: templ_7745c5c3_CSSID,
- Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
- }
-}
+// Constant class.
-func Button(text string) templ.Component {
+func StyleTagsAreSupported() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
@@ -64,29 +25,29 @@ func Button(text string) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- var templ_7745c5c3_Var2 = []any{className(), templ.Class("&&&unsafe"), "safe", templ.SafeClass("safe2")}
- templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var2).String()))
+ templ_7745c5c3_Var2 := `
+ .test {
+ color: #ff0000;
+ }
+ `
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" type=\"button\">")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var3 string = text
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
+ templ_7745c5c3_Var3 := `Style tags are supported`
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -97,7 +58,21 @@ func Button(text string) templ.Component {
})
}
-func LegacySupport() templ.Component {
+// CSS components.
+
+const red = "#00ff00"
+
+func cssComponentGreen() templ.CSSClass {
+ var templ_7745c5c3_CSSBuilder strings.Builder
+ templ_7745c5c3_CSSBuilder.WriteString(string(templ.SanitizeCSS(`color`, red)))
+ templ_7745c5c3_CSSID := templ.CSSID(`cssComponentGreen`, templ_7745c5c3_CSSBuilder.String())
+ return templ.ComponentCSSClass{
+ ID: templ_7745c5c3_CSSID,
+ Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
+ }
+}
+
+func CSSComponentsAreSupported() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
@@ -110,7 +85,7 @@ func LegacySupport() templ.Component {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- var templ_7745c5c3_Var5 = []any{templ.Classes(templ.Class("test"), "a")}
+ var templ_7745c5c3_Var5 = []any{cssComponentGreen()}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
@@ -123,7 +98,16 @@ func LegacySupport() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var6 := `CSS components are supported`
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -134,7 +118,10 @@ func LegacySupport() templ.Component {
})
}
-func MapCSSExample() templ.Component {
+// Both CSS components and constants are supported.
+// Only string names are really required. There is no need to use templ.Class or templ.SafeClass.
+
+func CSSComponentsAndConstantsAreSupported() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
@@ -142,13 +129,13 @@ func MapCSSExample() templ.Component {
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var6 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var6 == nil {
- templ_7745c5c3_Var6 = templ.NopComponent
+ templ_7745c5c3_Var7 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var7 == nil {
+ templ_7745c5c3_Var7 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- var templ_7745c5c3_Var7 = []any{map[string]bool{"a": true, "b": false, "c": true}}
- templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var7...)
+ var templ_7745c5c3_Var8 = []any{cssComponentGreen(), "classA", templ.Class("&&&classB"), templ.SafeClass("classC"), "d e"}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -156,11 +143,46 @@ func MapCSSExample() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var7).String()))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var8).String()))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" type=\"button\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var9 := `Both CSS components and constants are supported`
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var10 = []any{templ.Classes(cssComponentGreen(), "classA", templ.Class("&&&classB"), templ.SafeClass("classC")), "d e"}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var11 := `Both CSS components and constants are supported`
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">
")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -171,7 +193,9 @@ func MapCSSExample() templ.Component {
})
}
-func KVExample() templ.Component {
+// Maps can be used to determine if a class should be added or not.
+
+func MapsCanBeUsedToConditionallySetClasses() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
@@ -179,13 +203,13 @@ func KVExample() templ.Component {
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var8 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var8 == nil {
- templ_7745c5c3_Var8 = templ.NopComponent
+ templ_7745c5c3_Var12 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var12 == nil {
+ templ_7745c5c3_Var12 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- var templ_7745c5c3_Var9 = []any{"a", templ.KV("b", false)}
- templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...)
+ var templ_7745c5c3_Var13 = []any{map[string]bool{"a": true, "b": false, "c": true}}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var13...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -193,28 +217,88 @@ func KVExample() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var9).String()))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var13).String()))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var10 = []any{"a", "b", "c", templ.KV("c", false), templ.KV(d(), false), templ.KV(e(), true)}
- templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...)
+ templ_7745c5c3_Var14 := `Maps can be used to determine if a class should be added or not.`
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var10).String()))
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+// The templ.KV function can be used to add a class if a condition is true.
+
+func d() templ.CSSClass {
+ var templ_7745c5c3_CSSBuilder strings.Builder
+ templ_7745c5c3_CSSBuilder.WriteString(`font-size:12pt;`)
+ templ_7745c5c3_CSSID := templ.CSSID(`d`, templ_7745c5c3_CSSBuilder.String())
+ return templ.ComponentCSSClass{
+ ID: templ_7745c5c3_CSSID,
+ Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
+ }
+}
+
+func e() templ.CSSClass {
+ var templ_7745c5c3_CSSBuilder strings.Builder
+ templ_7745c5c3_CSSBuilder.WriteString(`font-size:14pt;`)
+ templ_7745c5c3_CSSID := templ.CSSID(`e`, templ_7745c5c3_CSSBuilder.String())
+ return templ.ComponentCSSClass{
+ ID: templ_7745c5c3_CSSID,
+ Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
+ }
+}
+
+func KVCanBeUsedToConditionallySetClasses() templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ if !templ_7745c5c3_IsBuffer {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var15 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var15 == nil {
+ templ_7745c5c3_Var15 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ var templ_7745c5c3_Var16 = []any{"a", templ.KV("b", false), "c", templ.KV(d(), false), templ.KV(e(), true)}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var16...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" placeholder=\"your@email.com\" autocomplete=\"off\">")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var17 := `KV can be used to conditionally set classes.`
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -225,7 +309,9 @@ func KVExample() templ.Component {
})
}
-func PsuedoAttributes() templ.Component {
+// Pseudo attributes can be used without any special syntax.
+
+func PsuedoAttributesAndComplexClassNamesAreSupported() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
@@ -233,21 +319,21 @@ func PsuedoAttributes() templ.Component {
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var11 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var11 == nil {
- templ_7745c5c3_Var11 = templ.NopComponent
+ templ_7745c5c3_Var18 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var18 == nil {
+ templ_7745c5c3_Var18 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- var templ_7745c5c3_Var12 = []any{"bg-violet-500", templ.KV(templ.SafeClass("hover:bg-violet-600"), true)}
- templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...)
+ var templ_7745c5c3_Var19 = []any{"bg-violet-500", "hover:bg-red-600", "hover:bg-sky-700", "text-[#50d71e]", "w-[calc(100%-4rem)"}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var19...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -271,7 +357,9 @@ func PsuedoAttributes() templ.Component {
})
}
-func ThreeButtons() templ.Component {
+// Class names are HTML escaped.
+
+func ClassNamesAreHTMLEscaped() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
@@ -279,71 +367,84 @@ func ThreeButtons() templ.Component {
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var14 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var14 == nil {
- templ_7745c5c3_Var14 = templ.NopComponent
+ templ_7745c5c3_Var21 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var21 == nil {
+ templ_7745c5c3_Var21 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var22).String()))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = Button("A").Render(ctx, templ_7745c5c3_Buffer)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = Button("B").Render(ctx, templ_7745c5c3_Buffer)
+ templ_7745c5c3_Var23 := `Class names are HTML escaped.`
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var16 = []any{templ.Classes(green)}
- templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var16...)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ templ_7745c5c3_Err = CSSComponentsAreSupported().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var17 string = "Green"
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
+ templ_7745c5c3_Err = CSSComponentsAndConstantsAreSupported().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
+ templ_7745c5c3_Err = MapsCanBeUsedToConditionallySetClasses().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = MapCSSExample().Render(ctx, templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = KVCanBeUsedToConditionallySetClasses().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = KVExample().Render(ctx, templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = PsuedoAttributesAndComplexClassNamesAreSupported().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = PsuedoAttributes().Render(ctx, templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = ClassNamesAreHTMLEscaped().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/runtime.go b/runtime.go
index 5db606eaa..75a694cc1 100644
--- a/runtime.go
+++ b/runtime.go
@@ -10,7 +10,6 @@ import (
"html"
"io"
"net/http"
- "regexp"
"sort"
"strings"
"sync"
@@ -164,14 +163,14 @@ func (cp *cssProcessor) Add(item any) {
switch c := item.(type) {
case []string:
for _, className := range c {
- cp.AddUnsanitized(className, true)
+ cp.AddClassName(className, true)
}
case string:
- cp.AddUnsanitized(c, true)
+ cp.AddClassName(c, true)
case ConstantCSSClass:
- cp.AddSanitized(c.ClassName(), true)
+ cp.AddClassName(c.ClassName(), true)
case ComponentCSSClass:
- cp.AddSanitized(c.ClassName(), true)
+ cp.AddClassName(c.ClassName(), true)
case map[string]bool:
// In Go, map keys are iterated in a randomized order.
// So the keys in the map must be sorted to produce consistent output.
@@ -183,43 +182,32 @@ func (cp *cssProcessor) Add(item any) {
}
sort.Strings(keys)
for _, className := range keys {
- cp.AddUnsanitized(className, c[className])
+ cp.AddClassName(className, c[className])
}
case []KeyValue[string, bool]:
for _, kv := range c {
- cp.AddUnsanitized(kv.Key, kv.Value)
+ cp.AddClassName(kv.Key, kv.Value)
}
case KeyValue[string, bool]:
- cp.AddUnsanitized(c.Key, c.Value)
+ cp.AddClassName(c.Key, c.Value)
case []KeyValue[CSSClass, bool]:
for _, kv := range c {
- cp.AddSanitized(kv.Key.ClassName(), kv.Value)
+ cp.AddClassName(kv.Key.ClassName(), kv.Value)
}
case KeyValue[CSSClass, bool]:
- cp.AddSanitized(c.Key.ClassName(), c.Value)
+ cp.AddClassName(c.Key.ClassName(), c.Value)
case CSSClasses:
for _, item := range c {
cp.Add(item)
}
case func() CSSClass:
- cp.AddSanitized(c().ClassName(), true)
+ cp.AddClassName(c().ClassName(), true)
default:
- cp.AddSanitized(unknownTypeClassName, true)
+ cp.AddClassName(unknownTypeClassName, true)
}
}
-func (cp *cssProcessor) AddUnsanitized(className string, enabled bool) {
- for _, className := range strings.Split(className, " ") {
- className = strings.TrimSpace(className)
- if isSafe := safeClassName.MatchString(className); !isSafe {
- className = fallbackClassName
- enabled = true // Always display the fallback classname.
- }
- cp.AddSanitized(className, enabled)
- }
-}
-
-func (cp *cssProcessor) AddSanitized(className string, enabled bool) {
+func (cp *cssProcessor) AddClassName(className string, enabled bool) {
cp.classNameToEnabled[className] = enabled
cp.orderedNames = append(cp.orderedNames, className)
}
@@ -256,20 +244,16 @@ func KV[TKey comparable, TValue any](key TKey, value TValue) KeyValue[TKey, TVal
}
}
-var safeClassName = regexp.MustCompile(`^-?[_a-zA-Z]+[-_a-zA-Z0-9]*$`)
-
-const fallbackClassName = "--templ-css-class-safe-name"
const unknownTypeClassName = "--templ-css-class-unknown-type"
-// Class returns a sanitized CSS class name.
+// Class returns a CSS class name.
+// Deprecated: use a string instead.
func Class(name string) CSSClass {
- if !safeClassName.MatchString(name) {
- return SafeClass(fallbackClassName)
- }
return SafeClass(name)
}
// SafeClass bypasses CSS class name validation.
+// Deprecated: use a string instead.
func SafeClass(name string) CSSClass {
return ConstantCSSClass(name)
}
@@ -280,6 +264,7 @@ type CSSClass interface {
}
// ConstantCSSClass is a string constant of a CSS class name.
+// Deprecated: use a string instead.
type ConstantCSSClass string
// ClassName of the CSS class.
diff --git a/runtime_test.go b/runtime_test.go
index b5b04cef4..2277d7985 100644
--- a/runtime_test.go
+++ b/runtime_test.go
@@ -244,43 +244,6 @@ func TestRenderCSS(t *testing.T) {
}
}
-func TestClassSanitization(t *testing.T) {
- tests := []struct {
- input string
- expected string
- }{
- {
- input: `safe`,
- expected: `safe`,
- },
- {
- input: `safe-name`,
- expected: "safe-name",
- },
- {
- input: `safe_name`,
- expected: "safe_name",
- },
- {
- input: `!unsafe`,
- expected: "--templ-css-class-safe-name",
- },
- {
- input: ``,
- expected: "--templ-css-class-safe-name",
- },
- }
- for _, tt := range tests {
- tt := tt
- t.Run(tt.input, func(t *testing.T) {
- actual := templ.Class(tt.input)
- if actual.ClassName() != tt.expected {
- t.Errorf("expected %q, got %q", tt.expected, actual.ClassName())
- }
- })
- }
-}
-
func TestClassesFunction(t *testing.T) {
tests := []struct {
name string
@@ -288,14 +251,9 @@ func TestClassesFunction(t *testing.T) {
expected string
}{
{
- name: "safe constants are allowed",
- input: []any{"a", "b", "c"},
- expected: "a b c",
- },
- {
- name: "unsafe constants are filtered",
- input: []any{"", "b", ""},
- expected: "--templ-css-class-safe-name b",
+ name: "constants are allowed",
+ input: []any{"a", "b", "c", ""},
+ expected: "a b c ",
},
{
name: "legacy CSS types are supported",
@@ -341,25 +299,17 @@ func TestClassesFunction(t *testing.T) {
{
name: "string arrays are supported",
input: []any{
- []string{"a", "b", "c"},
- "d",
- },
- expected: "a b c d",
- },
- {
- name: "string arrays are checked for unsafe class names",
- input: []any{
- []string{"a", "b", "c "},
+ []string{"a", "b", "c", ""},
"d",
},
- expected: "a b c --templ-css-class-safe-name d",
+ expected: "a b c d",
},
{
name: "strings are broken up",
input: []any{
"a ",
},
- expected: "a --templ-css-class-safe-name",
+ expected: "a ",
},
{
name: "if a templ.CSSClasses is passed in, the nested CSSClasses are extracted",