variablesjavafxjavafx-css

How do I correctly use variables in JavaFX CSS Stylesheets?


I am building a small password manager using JavaFX and currently working on make it easy to change the color theme. For that, I declared variables containing the hexcodes of different colors and saved'em in a stylesheet called "utility.css". This stylesheet is imported in two different stylesheets and the variables are called as described here.

Somehow I get this error while parsing:

Feb. 21, 2023 5:32:48 PM javafx.css.CssParser parse
WARNING: CSS Error parsing file:/G:/.../Coding/.../target/classes/css/utility.css: Expected RBRACE at [3,4]
Feb. 21, 2023 5:32:48 PM javafx.css.CssParser term
WARNING: CSS Error parsing file:/G:/.../Coding/.../target/classes/css/login-menu.css: Unexpected token '-' at [4,30]
Feb. 21, 2023 5:32:48 PM javafx.css.CssParser term
WARNING: CSS Error parsing file:/G:/.../Coding/.../target/classes/css/login-menu.css: Unexpected token '-' at [4,30]
Feb. 21, 2023 5:32:48 PM javafx.css.CssParser reportException
WARNING: Please report java.lang.NullPointerException at:
    javafx.graphics@19/javafx.css.CssParser.term(CssParser.java:4689)
    javafx.graphics@19/javafx.css.CssParser.expr(CssParser.java:4583)
    javafx.graphics@19/javafx.css.CssParser.declaration(CssParser.java:4554)
    javafx.graphics@19/javafx.css.CssParser.declarations(CssParser.java:4472)
    javafx.graphics@19/javafx.css.CssParser.parse(CssParser.java:3981)
    javafx.graphics@19/javafx.css.CssParser.parse(CssParser.java:253)
    javafx.graphics@19/javafx.css.CssParser.parse(CssParser.java:241)

I already asked chatGPT but seems there are things, AI can't help us with (yet) :D

This is the code of the "utility.css"-file, where I declared the variables

:root {
    --maincolor: #2b284d;
    --backgrounddark: #2b284d;
    --backgroundlight: #5a4f7d;
    --panel: #3c325c;
    --panelhighlighted: #503d6b;
    --panelselected: #503d6f;
    --casualbutton: #2b284d;
    --casualbuttonhighlighted: #5d5478;
    --crucialbuttonhightled: #eb5b0e;
    --textfield: #2b288d;
    --textfill: #b5b5b5;
}

and this of the two stylesheets:

@import "utility.css";

.combo-box .list-cell:filled:selected {
    -fx-background-color: var(--panelhighlighted);
    -fx-text-fill: #b5b5b5 ;
    -fx-font-family: "Calibri Light" ;
    -fx-font-size: 17;
    -fx-background-radius: 20px;
}

.combo-box .combo-box-popup .list-view, .combo-box .combo-box-popup .list-cell {
    -fx-background-color: var(--panel);
    -fx-text-fill: var(--textfill) ;
    -fx-font-family: "Calibri Light" ;
    -fx-font-size: 17;
    -fx-background-radius: 20px;
}

.combo-box .combo-box-popup .list-cell:hover {
    -fx-text-fill:  var(--textfill);
    -fx-background-color: var(--panelhighlighted);
}

.password-field {
    -fx-text-fill: #b5b5b5 ;
    -fx-prompt-text-fill: #b5b5b5 ;
}

.anchor-pane {
    overflow:hidden;
    -fx-background-color: #2b284d;
}

and

@import "utility.css";

.list-cell:filled:selected:focused, .list-cell:filled:selected {
    -fx-background-color: var(--panelselected);
}

.list-cell:filled:hover {
    -fx-background-color: var(--panelhighlighted);
}

.list-cell {
    -fx-background-color: var(--panel);
    -fx-pref-height: 60;
    -fx-background-radius: 20px;
    -fx-background-insets: 0;
    -fx-text-fill: var(--textfill) ;
    -fx-font-family: "Calibri Light" ;
    -fx-font-size: 20;
    -fx-alignment: center-left;
}

.list-view {
    -fx-background-color: var(--backgrounddark);
    -fx-border-color: var(--backgrounddark);
    -fx-background-insets: 0;
    -fx-padding: 0px;
}

.password-field {
    -fx-text-fill: var(--textfill) ;
    -fx-prompt-text-fill: var(--textfill) ;
    -fx-background-radius: 20px;
    -fx-background-color: var(--textfield);
}

.label {
    -fx-text-fill: var(--textfill);
    -fx-background-color: var(--textfield);
}

So I tried to eliminate '-' in the variable names, because these are used in css as the start of a command or sth but this didn't work out. I also tried an online css debugging tool, which only said I should switch the import statement with something called link to improve performance, which is not important for me atm.

I have absolutely no idea, what's the problem here. I am using IntelliJ 2022.3.1 (Ultimate Edition). The error happens at runtime, before that, IntelliJ doesn't complain at all and even auto-completes my variable names in the stylesheets, so they are definitely there and definitely correct.

The error appears to happen, when the css parser reaches the firtst '-' in the first stylesheet that calls a variable, as if this may not happen there.


Solution

  • Generally, CSS variables are not supported in JavaFX CSS. However, JavaFX CSS does support a (non-standard) mechanism called "looked-up colors" which effectively provides the ability to define color variables. This is documented here. The default modena.css stylesheet uses many looked-up colors, many of which are dependent on each other, to allow you to "theme" an application fairly readily by changing just a small number of the predefined looked-up colors. The current implementation of modena is here. See this question for an example of using this approach to "Theme" an application.

    The rest of this answer shows you why your current solution is not working and how to use looked-up colors to achieve the same goal.

    Glancing quickly at the CSS Specification, it looks like a valid identifier can only have a single hyphen at the start:

    Macro     Definition
    ident     [-]?{nmstart}{nmchar}*
    name      {nmchar}+
    nmstart   [_a-z]|{nonascii}|{escape}
    

    The : in a selector signifies a pseudoclass. In HTML, the document root is assigned a pseudoclass called root. In JavaFX, the root of a scene is assigned a CSS class called root. So if you want the looked-up colors to be applied to the root pane and inherited by everything in the application, use the class selector for the root (.root). Note that :root is syntactically valid as a selector, but since you likely don't have anything with the root pseudoclass, it will generate errors at runtime because the looked-up colors won't be found (there is no node for which those looked-up colors are defined).

    I did a quick test with all the looked-up color names renamed to a start with a single hyphen, and changed the selector to select the root:

    .root {
        -maincolor: #2b284d;
        -backgrounddark: #2b284d;
        -backgroundlight: #5a4f7d;
        -panel: #3c325c;
        -panelhighlighted: #503d6b;
        -panelselected: #503d6f;
        -casualbutton: #2b284d;
        -casualbuttonhighlighted: #5d5478;
        -crucialbuttonhightled: #eb5b0e;
        -textfield: #2b288d;
        -textfill: #b5b5b5;
    }
    

    Note that CSS naming conventions recommend separating words with hyphens, so your looked-up color names should probably be something like -main-color, etc. You might also consider putting a vendor prefix on them to avoid name collisions.

    There is no need for var(...) in JavaFX CSS to reference a looked-up color (and I believe the parser will not recognize it). Just reference these directly. See the JavaFX CSS documentation.

    I tested this with a CSS stylesheet style.css as follows:

    @import "utility.css";
    .label {
        -fx-background-color: -backgrounddark;
    }
    

    and ran it with IntelliJ's "default" JavaFX template (which I included below), and it worked as expected with no errors.

    HelloApplication.java:

    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    import java.io.IOException;
    
    public class HelloApplication extends Application {
        @Override
        public void start(Stage stage) throws IOException {
            FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
            Scene scene = new Scene(fxmlLoader.load(), 320, 240);
            scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
            stage.setTitle("Hello!");
            stage.setScene(scene);
            stage.show();
        }
    
        public static void main(String[] args) {
            launch();
        }
    }
    

    HelloController.java:

    import javafx.fxml.FXML;
    import javafx.scene.control.Label;
    
    public class HelloController {
        @FXML
        private Label welcomeText;
    
        @FXML
        protected void onHelloButtonClick() {
            welcomeText.setText("Welcome to JavaFX Application!");
        }
    }
    

    and hello-view.fxml:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.geometry.Insets?>
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.layout.VBox?>
    
    <?import javafx.scene.control.Button?>
    <VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml"
          fx:controller="org.jamesd.examples.csstest.HelloController">
        <padding>
            <Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
        </padding>
    
        <Label fx:id="welcomeText"/>
        <Button text="Hello!" onAction="#onHelloButtonClick"/>
    </VBox>