dotnet add package CssInCSharp
Add usings to _Imports.razor file in your blazor project.
@using CssInCSharp
// Create css object
var css = new CSSObject
{
[".demo"] = new CSSObject
{
Width = 300,
Height = 300,
Border = "1px solid #DDD",
["& .title"] = new CSSObject
{
LineHeight = 20,
Color = "red"
},
["& .button"] = new CSSObject
{
Width = "100%",
Height = "20px",
TextAlign = "center",
["&:hover"] = new CSSObject
{
Color = "blue"
}
}
}
};
// Serialize the css object.
var style = css.ToString();
// or use hashId
var style = css.SerializeCss("hashId");
The CSSObject type supports a string indexer, which you can add calss names, attributes, selectors, and variables.
var css = new CSSObject
{
// class name
[".btn-default"] = new CSSObject
{
// selectors
["&:hober"] = ...,
["> span"] = ...,
["button"] = ...,
// css variable
["--btn-display"] = "block",
},
}
The CSSObject defines a property type for each style property, which supports a wide range of types.
var css = new CSSObject
{
[".btn-default"] = new CSSObject
{
// different ways to set the value
Width = 200,
Width = "200px",
Width = "100%",
Width = "var(--btn-width)",
},
}
The width of the number type is automatically wrapped with px
at the time of serialization.
The benefit of numeric types is that you can use operators for style attributes.
var token = 10;
var css = new CSSObject
{
[".btn-default"] = new CSSObject
{
// eg: use four operations + - * /
Width = 200 + token,
Width = 200 - token,
Width = 200 * token,
Width = 200 / token,
},
}
The stylesheet is structured, this is similar to less or sass.
HTML
<div class="div1">
<div class="div2">
<div class="div3"></div>
</div>
</div>
Standard CSS
.div1 { ... }
.div1 .div2 { ... }
.div1 .div2 .div3 { ... }
CssInCSharp
var css = new CSSObject
{
[".div1"] = new CSSObject
{
[".div2"] = new CSSObject
{
[".div3"] = new CSSObject
{
}
}
}
}
Referencing parent selectors with & , This is the same as less.
var css = new CSSObject
{
["a"] = new CSSObject
{
Color = "blue",
["&:hover"] = new CSSObject
{
Color = "green"
}
}
};
output:
a {
color: blue;
}
a:hover {
color: green;
}
The parent selectors
operator has a variety of uses. Basically any time you need the selectors of the nested rules to be combined in other ways than the default. For example another typical use of the & is to produce repetitive class names:
var css = new CSSObject
{
[".button"] = new CSSObject
{
["&-ok"] = new CSSObject
{
BackgroundImage = "url(\"ok.png\")"
},
["&-cancel"] = new CSSObject
{
BackgroundImage = "url(\"cancel.png\")"
},
["&-custom"] = new CSSObject
{
BackgroundImage = "url(\"custom.png\")"
}
}
};
output:
.button-ok {
background-image: url("ok.png");
}
.button-cancel {
background-image: url("cancel.png");
}
.button-custom {
background-image: url("custom.png");
}
You can use the merge method to inherit or merge multiple css object in some scenarios.
var globalCss = new CSSObject
{
Background = "#EFEFEF",
FontSize = "14px",
Border = "1px solid #DDD",
MarginBottom = "20px"
};
// merge with a global css
var css = new CSSObject
{
[".div1"] = new CSSObject
{
Width = "100px",
Height = "100px",
Color = "red"
}.Merge(globalCss),
[".div2"] = new CSSObject
{
Width = "120px",
Height = "120px",
Color = "blue"
}.Merge(globalCss),
[".div3"] = new CSSObject
{
Width = "120px",
Height = "120px",
Color = "blue",
["& .title"] = new CSSObject
{
Color = "cadetblue",
FontSize = "20px"
}
}.Merge(globalCss)
};
var globalCss = new CSSObject
{
Background = "#EFEFEF",
FontSize = "14px",
Border = "1px solid #DDD",
MarginBottom = "20px"
};
var colorCss = new CSSObject
{
Color = "green"
}
var css = new CSSObject
{
[".div1"] = new CSSObject
{
// using "..." to merge globalCss, similar to the ...globalCss in ts.
["..."] = globalCss,
Width = "100px",
Height = "100px",
Color = "red",
["..."] = colorCss, // merge operator can be used multiple times.
}
};
NOTE : The Merge method can only overwrite the object property after instantiation, but the Merge operator can be overridden during instantiation.
// override div3 title color
css.Merge(new CSSObject
{
[".div3"] = new CSSObject
{
["& .title"] = new CSSObject
{
Color = "yellow"
}
}
});
You can use the variables defined in your class during css object creation, including member variables, local variables etc.
private string _style = "";
private string _color = "red"; // field
[Parameter]
public int Size { get; set; } = 200; // component parameter
protected override void OnInitialized()
{
var fontSize = "16px"; // local varibale
var css = new CSSObject
{
[".div1"] = new CSSObject
{
Border = "1px solid #DDD",
Width = $"{Size}px",
Height = $"{Size}px",
Color = _color,
FontSize = fontSize,
["& .title"] = new CSSObject
{
Color = _color,
FontSize = fontSize
}
}
};
// variables are effective only during initialization
_style = css.ToString();
}
Variables are effective only during initialization, you should not change the css object by modifying variables after initialization.
private CSSObject _css = new ();
private string _color = "red";
[Parameter]
public int Size { get; set; } = 200; // component parameter
protected override void OnInitialized()
{
var fontSize = "16px"; // local varibale
_css = new CSSObject
{
[".div2"] = new CSSObject
{
Border = "1px solid #DDD",
Width = $"{Size}px",
Height = $"{Size}px",
MarginTop = "10px",
["& .title"] = new CSSObject
{
Color = _color,
FontSize = fontSize
}
}
};
}
If you want to change css object after initialization, you should define css object as a member variable, but we do not recommend you to do this.
The following examples shows how to change a css object.
private void ClickToChangeCssObject()
{
// 1.use merge method to change css object, @see merge example.
_css.Merge(new CSSObject
{
[".div2"] = new ()
{
["& .title"] = new CSSObject
{
Color = "green"
}
}
});
// 2.or set property directly
_css[".div2"]["& .title"].Color = "green";
// rerender the style
_style = _css.ToString();
}
Remember: Never change the css object after initialization.
As you know, Functions are also supported.
private int _size = 500;
private string _style = "";
protected override async Task OnInitializedAsync()
{
var css = new CSSObject
{
[".div"] = new CSSObject
{
Width = $"{_size}px",
Border = "1px solid #DDD",
["& .header"] = new CSSObject
{
Height = "50px",
Width = "100%",
},
["& .footer"] = new CSSObject
{
Height = "50px",
Width = "100%",
},
["& .body"] = new CSSObject
{
// normal method
Height = $"{CalcBodyHeight(50, 50)}px",
Width = "100%",
BorderTop = "1px solid #DDD",
BorderBottom = "1px solid #DDD",
[".container"] = new CSSObject
{
// async method
Height = await GetContainerSizeAsync(),
BackgroundColor = "#EFEFEF",
}
}
}
};
_style = css.ToString();
}
private int CalcBodyHeight(int header, int footer)
{
return _size - header - footer;
}
private async Task<string> GetContainerSizeAsync()
{
return await Task.FromResult("200px");
}
We designed a Keyframe type to create animation styles, which are supported by the AnimationName property.
Transform animation example:
<div class="transform">
<span class="title">Transform</span>
<div class="animation"></div>
</div>
<style>
@_transform
</style>
@code {
private string _transform = "";
protected override void OnInitialized()
{
_transform = new CSSObject
{
[".transform"] = new CSSObject
{
Width = 120,
Height = 120,
["& .title"] = new CSSObject
{
Height = 20,
LineHeight = 20,
FontSize = 14
},
["& .animation"] = new CSSObject
{
Width = 100,
Height = 100,
BackgroundColor = "rgba(0, 0, 255, 0.5)",
AnimationDuration = "3s",
AnimationName = new Keyframe("animation-transform")
{
["from"] = new()
{
Transform = "translateX(0px)",
Opacity = 1
},
["to"] = new()
{
Transform = "translateX(100px)",
Opacity = 0.2f
}
}
}
}
}.ToString();
}
}
CssInCSharp provider a TinyColor type for color manipulation and conversion.
// convert rgb string to hex
new TinyColor("rgb 255 0 0").ToHexString(); // should be: #ff0000
// convert hex to rgb
new TinyColor("#fff").ToRgbString(); // should be: rgb(255, 255, 255)
// convert css color to hex
new TinyColor("red").ToHexString(); // should be: #ff0000
// from rgb
new TinyColor(new RGB(255, 0, 0)).ToHexString(); // should be: #ff0000
// set alpha
new TinyColor("rgba(255, 0, 0, 1)").SetAlpha(0.9);
The CssInCSharp lib provides a set of components for style rendering. Including <StyleOutlet>
, <StyleContent>
and <Style>
.
The <StyleOutlet>
component is the entry component of the style, if you want to render the style to the head tag, you need to put this component in the head.
In wasm project, add this code to Program.cs
file.
// builder.RootComponents.Add<HeadOutlet>("head::after"); // remove this line
builder.RootComponents.Add<StyleOutlet>("head::after"); // add StyleOutlet to head
In server project, add this code to _Layout.cshtml
file.
<head>
<!--add StyleOutlet here, is should be on the top-->
<!--also should remove HeadOutlet-->
<component type="typeof(StyleOutlet)" render-mode="ServerPrerendered" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
...
</head>
The content of the <StyleContent>
will be rendered into the <StyleOutlet>
. StyleContent will usually be placed on the page. Each page can only contain one StyleContent, if there are multiple ones, only the last rendered will take effect.
@page "/"
<div></div>
<PageTitle>Home</PageTitle>
<StyleContent>
<!--put your style here-->
</StyleContent>
@code{
}
The <Style>
component provides style tag generation, style caching, and style uniqueness.
Style Component Properties:
Name | Type | Desc |
---|---|---|
HashId | string | Hash of style token. |
Path | string | Path of style. |
StyleFn | Func | Style generation method. |
@page "/"
<div class="div1"></div>
<PageTitle>Home</PageTitle>
<StyleContent>
<Style HashId="@_tokenHash" Path="home|button" StyleFn="@StyleFn"></Style>
</StyleContent>
@code
{
private string _tokenHash = "css-zcfrx9";
private CSSInterpolation GenStyle()
{
return new CSSObject
{
[".div1"] = new CSSObject
{
Width = "200px",
Height = "200px",
Border = "1px solid #DDD",
}
};
}
}
NOTE:
The Path
provides uniqueness for the style, only one style tag will be created of the same path. When designing a component, you should add a Path to the component style, so that no matter how many instances the component creating, there is only one stylesheet for those instances.
When style is generated, the <Style>
component will create a cache for the generated style based on the Path
. When component is updated, if the style is cached, StyleFn
will no longer execute, but will take the cached value.
The Style
component uses the :where
pseudo-class to implement style isolation. If you need to enable style isolation, just set the value for the HashId
.
<div class="@_tokenHash div1"></div>
<StyleContent>
<Style HashId="@_tokenHash" Path="home|button" StyleFn="@StyleFn"></Style>
</StyleContent>
@code{
private string _tokenHash = "css-zcfrx9";
private CSSInterpolation GenStyle()
{
return new CSSObject
{
[".div1"] = new CSSObject
{
Width = "200px",
Height = "200px",
Border = "1px solid #DDD",
}
};
}
}
The style output:
<style>
:where(.css-zcfrx9).div1{width:200px;height:200px;border:1px solid #DDD;}
</style>
On a page, you can use the <StyleContent>
component to add styles, but you can't use StyleContent in custom component, so if you need to add a style to the head in the custom component, you need to use the UseStyleRegister
method in the StyleHelper
class to register the style.
DemoComponent.razor
<div class="@_tokenHash demo"></div>
@_styleContent
@code
{
private RenderFragment _styleContent;
private string _tokenHash = "css-zcfrx9";
protected override void OnInitialized()
{
_styleContent = StyleHelper.UseStyleRegister(new StyleInfo
{
HashId = _tokenHash,
Path = new[] { "component", "demo" },
StyleFn = GenStyle
});
}
private CSSInterpolation GenStyle()
{
return new CSSObject
{
[".demo"] = new CSSObject
{
Width = "200px",
Height = "200px",
Border = "1px solid #DDD",
}
};
}
}