Processing...
Description & Source Code
  • Description
  • XML & Java Source
    css_variables.zul
    css_variables_ctrl.zul
    example-panel.zul
    example-window.zul
  • Controller

ZK Framework 10.3.0 introduces CSS Variables (Custom Properties) that allow you to dynamically customize component styling without rebuilding a custom theme.

Key CSS variables demonstrated:

  • --zk-color-primary - Primary accent color for buttons, links, and highlights
  • --zk-color-primary-dark - Darker shade of the primary color
  • --zk-color-background1 - General background color (e.g., page, content)
  • --zk-color-background3 - Section/Component background color
  • --zk-font-size-medium - Base font size affecting all components
  • --zk-base-title-font-family - Font family for text rendering
  • --zk-base-border-radius - Corner rounding for buttons, inputs, and panels

Changes are applied instantly via JavaScript by setting CSS properties on the root element.

All CSS Variables

You can see all ZK-defined CSS variables by checking the root element (<html>) with a browser developer tool.

css_variables.zul
<zk  xmlns:n="native" xmlns:c="client" xmlns:ca="client/attribute" xmlns:x="xhtml">
	<apply templateURI="/widgets/customize_theme/css_variables/example-window.zul"/>
	<n:br/>
	<apply templateURI="/widgets/customize_theme/css_variables/example-panel.zul"/>
</zk>
css_variables_ctrl.zul
<zk>
	<groupbox sclass="z-demo-config" closable="false" hflex="min" apply="demo.customize_theme.cssvar.CssVariablesCtrl">
		<vlayout spacing="12px">
		<button id="preset1" label="Natural Theme" width="140px"/>
		<button id="preset2" label="Honey Theme" width="140px"/>
		<label value="Customize Your Own Theme" style="display:block; font-weight:bold; font-size: medium"/>
			<vlayout spacing="3px">
				<label value="Primary Color"/>
				<colorbox id="colorPicker" width="30px" height="25px" color="#0093F9"/>
			</vlayout>
			<vlayout spacing="3px">
				<label value="Primary Dark Color"/>
				<colorbox id="primaryDarkPicker" width="30px" height="25px" color="#0074CC"/>
			</vlayout>
			<vlayout spacing="3px">
				<label value="Background 1"/>
				<colorbox id="bg1Picker" width="30px" height="25px" color="#FFFFFF"/>
			</vlayout>
			<vlayout spacing="3px">
				<label value="Background 3"/>
				<colorbox id="bg3Picker" width="30px" height="25px" color="#F5F5F5"/>
			</vlayout><!-- Font Size -->
			<vlayout spacing="3px">
				<label value="Font Size Medium"/>
				<hlayout valign="middle" spacing="5px">
					<slider id="fontSizeSlider" mold="scale" curpos="14" minpos="10" maxpos="24" width="100px"/>
					<label id="fontSizeLabel" value="14px"/>
				</hlayout>
			</vlayout>
			<vlayout spacing="3px">
				<label value="Title Font Family"/>
				<combobox id="fontFamilyCombo" width="140px" readonly="true" onCreate='self.selectedIndex=0' >
					<comboitem id="def" label="System Default" value="" />
					<comboitem label="Arial" value="Arial, sans-serif"/>
					<comboitem label="Georgia" value="Georgia, serif"/>
					<comboitem label="Courier New" value="Courier New, monospace"/>
					<comboitem label="Verdana" value="Verdana, sans-serif"/>
				</combobox>
			</vlayout>
			<vlayout spacing="3px">
				<label value="Base Border Radius"/>
				<hlayout valign="middle" spacing="5px">
					<slider id="borderRadiusSlider" mold="scale" curpos="4" minpos="0" maxpos="20" width="100px" />
					<label id="borderRadiusLabel" value="4px"/>
				</hlayout>
			</vlayout>
			<button id="resetBtn" label="Reset to Defaults" width="140px"/>
		</vlayout>
	</groupbox>
	<script src="/widgets/customize_theme/css_variables/css_variables.js"/>
</zk>
example-panel.zul
<panel title="Panel" border="normal" collapsible="true" maximizable="true" minimizable="true" closable="true" hflex="1" height="30vw" style="min-height: 500px;">
	<panelchildren>
		<vlayout height="100%" spacing="0">
			<menubar width="100%" vflex="min">
				<menuitem label="Menu"/>
				<menu label="File">
					<menupopup>
						<menuitem label="View"/>
						<menuitem iconSclass="z-icon-save" label="Save"/>
					</menupopup>
				</menu>
			</menubar>
			<borderlayout vflex="1">
				<west width="200px" splittable="true" collapsible="true" title="Help">
					<vlayout>
						<textbox hflex="1" placeholder="Placeholder"/>
						<timebox hflex="1" format="hh:mm:ss a" value="@init(vm.timeboxValue)"/>
						<bandbox hflex="1">
							<bandpopup>
								Any content
							</bandpopup>
						</bandbox>
						<button label="Default"/>
						<button label="Disabled" disabled="true"/>
						<a>a hyper link</a>
					</vlayout>
				</west>
				<center>
					<vlayout>
						<calendar/>
						${vm.loremIpsum}
					</vlayout>
				</center>
				<south height="150px" splittable="true" collapsible="true" title="Tag">
					<hlayout valign="middle">
						Default
						<chosenbox model="${vm.chosenModel}" width="350px"/>
					</hlayout>
				</south>
			</borderlayout>
		</vlayout>
	</panelchildren>
	<toolbar>
		<toolbarbutton label="Export"/>
		<combobutton label="Contact" mold="toolbar">
			<menupopup>
				<menuitem label="Phone"/>
				<menuitem label="Email"/>
				<menuitem label="Fax"/>
			</menupopup>
		</combobutton>
	</toolbar>
</panel>
example-window.zul
<window border="normal" title="Window" maximizable="true" minimizable="true" closable="true" hflex="1" height="30vw" style="min-height: 500px;">
	<vlayout spacing="10px" height="100%">
		<hlayout width="100%" vflex="1">
			<tabbox tabscroll="true" hflex="1" vflex="1">
				<tabs>
					<tab label="New"/>
					<tab label="All"/>
					<tab label="Done"/>
				</tabs>
				<tabpanels>
					<tabpanel>
						Updates
						<groupbox>
							<caption iconSclass="z-icon-volume-up" label="Caption"/>
							${vm.loremIpsum}
						</groupbox>
					</tabpanel>
					<tabpanel/>
					<tabpanel/>
				</tabpanels>
			</tabbox>
			<tabbox mold="accordion" hflex="1" vflex="1">
				<tabs>
					<tab label="Chart" closable="true"/>
					<tab label="Mail" closable="true"/>
					<tab label="File" closable="true"/>
				</tabs>
				<tabpanels>
					<tabpanel>
						<vlayout>
							<progressmeter width="100%" value="70"/>
							<colorbox value="#FFCC00"/>
						</vlayout>
					</tabpanel>
					<tabpanel/>
					<tabpanel/>
				</tabpanels>
			</tabbox>
		</hlayout>
		<groupbox vflex="1">
			<listbox multiple="true" checkmark="true" mold="paging" vflex="1">
				<listhead>
					<listheader label="Name"/>
					<listheader label="Location"/>
				</listhead>
				<listgroup label="group expanded" open="true"/>
				<listitem>
					<listcell label="Cindy"/>
					<listcell label="London"/>
				</listitem>
				<listitem>
					<listcell label="Mandy"/>
					<listcell label="Paris"/>
				</listitem>
				<listgroup label="group collapsed" open="false"/>
				<listitem>
					<listcell label="Mike"/>
					<listcell label="Tokyo"/>
				</listitem>
				<listitem>
					<listcell label="Ron"/>
					<listcell label="Detroit"/>
				</listitem>
				<listgroup label="group expanded" open="true"/>
				<listitem>
					<listcell label="Bob"/>
					<listcell label="Munich"/>
				</listitem>
				<listitem>
					<listcell label="John"/>
					<listcell label="Berlin"/>
				</listitem>
			</listbox>
		</groupbox>
	</vlayout>
</window>
CssVariablesCtrl.java
package demo.customize_theme.cssvar;

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.ScrollEvent;
import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.select.annotation.Listen;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zkex.zul.Colorbox;
import org.zkoss.zul.Combobox;
import org.zkoss.zul.Label;
import org.zkoss.zul.Slider;

import java.util.Map;

public class CssVariablesCtrl extends SelectorComposer<Component> {

    public static final String ZK_COLOR_PRIMARY = "--zk-color-primary";
    public static final String ZK_COLOR_PRIMARY_DARK = "--zk-color-primary-dark";
    public static final String ZK_COLOR_BACKGROUND_1 = "--zk-color-background1";
    public static final String ZK_COLOR_BACKGROUND_3 = "--zk-color-background3";
    public static final String ZK_FONT_SIZE_MEDIUM = "--zk-font-size-medium";
    public static final String ZK_BASE_TITLE_FONT_FAMILY = "--zk-base-title-font-family";
    public static final String ZK_BASE_BORDER_RADIUS = "--zk-base-border-radius";

    public static final String DEFAULT_FONT_FAMILY = "\"Helvetica Neue\", Helvetica, Arial, sans-serif";

    @Wire
    private Colorbox colorPicker;
    @Wire
    private Colorbox primaryDarkPicker;
    @Wire
    private Colorbox bg1Picker;
    @Wire
    private Colorbox bg3Picker;
    @Wire
    private Slider fontSizeSlider;
    @Wire
    private Label fontSizeLabel;
    @Wire
    private Combobox fontFamilyCombo;
    @Wire
    private Slider borderRadiusSlider;
    @Wire
    private Label borderRadiusLabel;

    @Listen("onChange = #colorPicker")
    public void onPrimaryColorChange() {
        String color = colorPicker.getValue();
        updateCssVar(ZK_COLOR_PRIMARY, color);
    }

    @Listen("onChange = #primaryDarkPicker")
    public void onPrimaryDarkColorChange() {
        String color = primaryDarkPicker.getValue();
        updateCssVar(ZK_COLOR_PRIMARY_DARK, color);
    }

    @Listen("onChange = #bg1Picker")
    public void onBg1ColorChange() {
        String color = bg1Picker.getValue();
        updateCssVar(ZK_COLOR_BACKGROUND_1, color);
    }

    @Listen("onChange = #bg3Picker")
    public void onBg3ColorChange() {
        String color = bg3Picker.getValue();
        updateCssVar(ZK_COLOR_BACKGROUND_3, color);
    }

    @Listen("onScroll = #fontSizeSlider")
    public void onFontSizeChange(ScrollEvent event) {
        int pos = event.getPos();
        fontSizeLabel.setValue(pos + "px");
        updateCssVar(ZK_FONT_SIZE_MEDIUM, pos + "px");
    }

    @Listen("onSelect = #fontFamilyCombo")
    public void onFontFamilyChange() {
        String value = fontFamilyCombo.getSelectedItem().getValue();
        if (value != null && !value.isEmpty()) {
            updateCssVar(ZK_BASE_TITLE_FONT_FAMILY, value);
        } else {
            updateCssVar(ZK_BASE_TITLE_FONT_FAMILY, DEFAULT_FONT_FAMILY);
        }
    }

    @Listen("onScroll = #borderRadiusSlider")
    public void onBorderRadiusChange(ScrollEvent event) {
        int pos = event.getPos();
        borderRadiusLabel.setValue(pos + "px");
        updateCssVar(ZK_BASE_BORDER_RADIUS, pos + "px");
    }

    private static final String RESET_CSS_VARS_SCRIPT =
            "var container = document.documentElement;" +
                    "['%s','%s','%s','%s','%s','%s','%s']" +
                    ".forEach(function(v){ container.style.removeProperty(v); });";

    @Listen("onClick = #resetBtn")
    public void resetCssVariables() {
        // Reset UI components
        colorPicker.setValue("#0093F9");
        primaryDarkPicker.setValue("#0074CC");
        bg1Picker.setValue("#FFFFFF");
        bg3Picker.setValue("#F5F5F5");
        fontSizeSlider.setCurpos(14);
        fontSizeLabel.setValue("14px");
        fontFamilyCombo.setSelectedIndex(0);
        borderRadiusSlider.setCurpos(4);
        borderRadiusLabel.setValue("4px");

        // Reset CSS variables using formatted constant
        Clients.evalJavaScript(String.format(RESET_CSS_VARS_SCRIPT,
                ZK_COLOR_PRIMARY,
                ZK_COLOR_PRIMARY_DARK,
                ZK_COLOR_BACKGROUND_1,
                ZK_COLOR_BACKGROUND_3,
                ZK_FONT_SIZE_MEDIUM,
                ZK_BASE_TITLE_FONT_FAMILY,
                ZK_BASE_BORDER_RADIUS));
    }

    private void updateCssVar(String name, String value) {
        Clients.evalJavaScript("updateCssVar('" + name + "', '" + value + "');");
    }


    //reference theme pack color
    static Map<String, String> NATURAL = Map.of(ZK_COLOR_PRIMARY, "#8AC73F",
            ZK_COLOR_PRIMARY_DARK, "#6EA22F",
            ZK_BASE_BORDER_RADIUS, "4px");

    @Listen("onClick = #preset1")
    public void applyOlive(){
        NATURAL.forEach(this::updateCssVar);
    }

    static Map<String, String> HONEY = Map.of(ZK_COLOR_PRIMARY, "#FFA706",
            ZK_COLOR_PRIMARY_DARK, "#D08700",
            ZK_BASE_BORDER_RADIUS, "12px");

    @Listen("onClick = #preset2")
    public void applyAmber(){
        HONEY.forEach(this::updateCssVar);
    }
}