Styling: CSS & GSS
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against GWT 2.13.1 (released 2026-06-19), gwt-maven-plugin 1.3.0 (net.ltgt.gwt.maven), JDK 21 running the compiler with -sourceLevel 17, the Jakarta Servlet 6 server of the Bookshelf scenario and the gwt-user 2.13.1 sources, checked on 2026-10-09.
It is mostly client knowledge: the style sheets are processed by the GWT compiler at build time (a deferred-binding generator turns every CssResource into a Java class) and injected into the page in the browser.
There is no server code.
Every output below was produced by compiling probe modules with the 2.13.1 compiler (-strict) and loading them in headless Chrome 155, and the Super Dev Mode statement at the end of the worked example was checked against a running code server on port 9911 (the probe pages were served from port 8911).
Where the page quotes a compiler message it is copied from those runs, and a few statements come from the Developer Guide or the 2.13.1 source only; those say so.
How a GWT page gets its styles
A GWT application is an ordinary web page, so everything the browser knows about CSS applies; GWT adds four ways to attach style sheets, a set of ready-made themes and, most importantly, a compiler pass for CSS. Plain CSS selectors, specificity and the cascade are not repeated here: the HTML & CSS selectors and specificity page covers them.
The left column is the choice you make; the middle column is what the compiler does per permutation; the right column is what the browser receives.
| Way | How | Status in the Developer Guide |
|---|---|---|
Host page |
|
Marked "Deprecated". Works, but the file is not part of the module and is not processed by GWT |
Module |
|
Marked "Deprecated". The file is found through the module’s public path and "follows the module" to any host page |
|
An interface that extends |
The modern way; this page’s main subject |
|
CSS written next to the markup that uses it, or loaded with |
The modern way, combined with |
The 2007 book on which part of this guide leans taught only the first two rows (a Dashboard.css file next to the module and <stylesheet>, plus setStyleName), because ClientBundle, CssResource, UiBinder and GSS did not exist yet.
That advice still works; this page shows what replaced it and why.
Style names on widgets
Every widget is a DOM element with a class attribute, and the widget classes manage that attribute with a small family of methods.
The Widgets page lists the methods and the probe that shows setStylePrimaryName renaming the dependent names too; this section adds the naming convention the themes rely on.
Each widget class has a primary style name of the form gwt-<ClassName> (gwt-Button, gwt-TextBox, gwt-Label), set by its constructor.
A dependent style name is the primary name plus a suffix and describes a state (gwt-Button with gwt-Button-busy after addStyleDependentName("busy")).
A secondary name is any other class you add with addStyleName.
Composite widgets put several names on several elements.
A probe in the Clean theme printed the class attributes of four composite widgets:
| Widget | Rendered class names |
|---|---|
|
|
|
|
|
|
read-only |
|
|
|
The Developer Guide’s MenuBar example (a gwt-MenuItem-selected suffix and the pair of names “gwt-MenuItem gwt-MenuItem-selected”) matches the observed output.
Which names a widget uses is documented in its Javadoc, not in one table: the guide says it is "standard practice" to document the relevant style names in each widget class’s comment, for example in UIObject and the widget classes.
When a widget has no documented hook, read the DOM produced by getElement().getString(), as Widgets does.
Two rules keep a theme and your own CSS working together:
-
Add your names with
addStyleNameand usesetStyleName(String)only when you mean to leave the theme (it replaces the whole attribute, so thegwt-primary name is gone and so are all theme rules) -
give your own widgets a primary name with a prefix of your project (
bookshelf-BookCard) and dependent names for state (bookshelf-BookCard-selected), the same convention the SDK uses, so that RTL and theme overrides can target them
The Developer Guide also covers addressing one element by id: GWT sets no id for you, so set it yourself on a static element in the host page or on a widget’s DOM element, and reference it as #my-button-id.
IDs must be unique in the page, which is why classes are the normal hook; ensureDebugId (see Widgets) is for tests.
Default themes
GWT ships four visual themes, and a new application inherits one of them in its module XML. The Developer Guide names three (Standard, Chrome and Dark, "by default, new GWT applications use the standard theme") and says each one also comes in an RTL version and a "Resources" version; the 2.13.1 source also contains a fourth, Clean, which the generated Bookshelf module uses.
| Theme | Module to inherit | Stylesheet it adds | Size on disk | RTL module |
|---|---|---|---|---|
Clean |
|
|
24,581 bytes |
|
Standard |
|
|
22,259 bytes |
|
Chrome |
|
|
22,430 bytes |
|
Dark |
|
|
20,481 bytes |
|
Each theme module is one line of substance: <stylesheet src="gwt/clean/clean.css"/> plus the public folder with the theme’s images (Clean has six small images).
The User module itself inherits no theme.
<module rename-to="bookshelf">
<inherits name="com.google.gwt.user.User"/>
<!-- choose at most one theme; the Bookshelf uses Clean -->
<inherits name="com.google.gwt.user.theme.clean.Clean"/>
<!-- <inherits name="com.google.gwt.user.theme.standard.Standard"/> -->
<!-- <inherits name="com.google.gwt.user.theme.chrome.Chrome"/> -->
<!-- <inherits name="com.google.gwt.user.theme.dark.Dark"/> -->
</module>
To see what a theme does I compiled six modules that differ only in the inherits line and printed what the browser applied to one Button, one TextBox and the page body:
| Module | Injected link |
Computed style |
|---|---|---|
Clean |
|
Button font |
Standard |
|
Button font Arial 13px, border 1px; text box border 2px inset |
Chrome |
|
as Standard (same Button border colour |
Dark |
|
body |
DarkRTL |
|
as Dark |
no theme |
none: no |
Button with the browser default (background |
Dropping or replacing a theme
To drop the themes, remove the inherits line; the page then only has the browser defaults plus whatever you add.
This is the normal choice for an application with its own design system, and for a design built by a team that wants to control every class.
Because the theme is a plain link to a file in the module, removing it does not change any Java code; widgets keep their gwt- class names, so your CSS can still target them.
Each theme also has a Resources variant (CleanResources, StandardResources, ChromeResources, DarkResources) described by the guide as "only the public resources associated with the theme, but does not inject a style sheet".
The probe confirmed it: a module that inherits StandardResources published gwt/standard/standard.css, gwt/standard/standard_rtl.css and the images folder into the output, and no link element was added to the page.
You copy that file into your own style sheet, remove what you do not need and attach the result with one of the four ways above; the guide recommends it for bandwidth-sensitive applications, where it puts the cost of a whole theme at "about 27k".
The measured sizes are 20 to 25 kilobytes uncompressed.
In the 2.13.1 source the Standard, Chrome and Dark Resources modules are empty <module/> files (the files are copied because they live in the theme’s public folder), while CleanResources inherits User and declares <public path="public"/>, so the effect is the same.
A third option is to keep a theme and override only some rules; the worked example does this by declaring the gwt- class names @external in a GSS file, so that the compiler leaves them unobfuscated.
CssResource
CssResource is the interface that represents one compiled style sheet.
You declare a subtype with one String accessor per CSS class, return it from a ClientBundle method and call ensureInjected() before the styles are needed.
The compiler reads the file, checks it against the interface, renames the classes and writes a Java class that returns the final text.
The ClientBundle & Images page covers the other resource types and the bundle machinery (@Source, inlining, caching); this page covers the CSS-specific part.
public interface BookshelfCss extends StateCss { // StateCss extends CssResource
String header();
@ClassName("low-stock") // CSS class is not a Java identifier
String lowStock();
String cover();
String ribbon();
int COVER_WIDTH(); // a numeric @def
}
public interface BookshelfResources extends ClientBundle {
BookshelfResources INSTANCE = GWT.create(BookshelfResources.class);
@Source("bookshelf.gss")
BookshelfCss css();
}
Facts worth knowing before the details:
-
There is one resource type,
CssResource. The Developer Guide talks about "GssResource", but there is no such class ingwt-user2.13.1: the interface isCssResourcefor both syntaxes (its annotation@DefaultExtensions({".css", ".gss"})lists both extensions) and the classCssResourceGeneratordelegates toGssResourceGeneratorwhenCssResource.enableGssis true. The page treats "GssResource" as that generator. -
ensureInjected()callsStyleInjector, which collects style sheets and flushes them withScheduler.scheduleFinally, so thestyleelement appears at the end of the current event loop, not in the call. In the probe,getComputedStyleread synchronously inonModuleLoadstill returned the theme colour; after a deferred command it returned the override. Repeated calls are no-ops and returntrueonly when they changed the DOM. -
getText()returns the final style sheet as a string, which is how the outputs on this page were captured. -
Without
@Sourcethe compiler looks for a file named after the accessor next to the interface:css()findscss.cssorcss.gss(withenableGsstrue the.gssfile is tried first). -
The generated class is created per permutation, so
@ifblocks onuser.agentorlocalebecome different text in different permutations (see the directives table).
Source: Javadoc — CssResource
Obfuscated class names
The compiler renames every class you reach through an accessor, so two style sheets can both define .title without clashing and unused rules can be dropped.
Strict checking is on by default: every class selector must have an accessor, and every accessor must have a class.
The names are meant to be used through the accessor, never typed:
copies.addStyleName(RES.css().lowStock()); // "EFLPHAC-a-d", not "low-stock"
The text of an obfuscated class depends on two configuration properties of the Resources module (both read from Resources.gwt.xml in 2.13.1):
| Property | Default | Meaning |
|---|---|---|
|
|
The obfuscation style. Source values: |
|
|
A prefix for every generated class. |
|
|
Multi-valued: class-name prefixes the obfuscator must never generate |
|
|
Set to |
|
|
Extra non-standard at-rules the parser accepts (multi-valued) |
|
empty |
Extra non-standard functions the parser accepts (multi-valued) |
Source: GWT 2.13.1 — Resources.gwt.xml
For the same GSS file, these were the names of the card accessor of the probe’s BookCss (all five compiled from one file):
| Setting | Class name |
|---|---|
default ( |
|
|
|
|
|
|
|
|
|
|
|
|
|
The stable* styles produce names that do not depend on the rest of the application, which makes them usable in tests and in tools that cache CSS.
The source explains why the compiler uses one of them: Super Dev Mode with incremental compilation (the default) overrides CssResource.style to stable (in Recompiler), and the worked example confirmed it: in Super Dev Mode the class names were com-example-bookshelf-client-style-BookshelfCss-low-stock instead of an obfuscated token.
Production builds use the default.
The -style PRETTY compiler flag formats the JavaScript only: a legacy module without CssResource.style printed minified, obfuscated CSS even when compiled with -style PRETTY. With CssResource.style = pretty, legacy CSS is also pretty-printed with line breaks, while GSS keeps its minified single line and only the class names become readable.
Scopes, ImportedWithPrefix, Shared
"Scope" in the guide is the set of obfuscated names that belong to one CssResource return type.
Two accessors returning the same interface share names; two different interfaces (even when they both declare title()) get different names.
// A state class that must have the same name in every stylesheet that extends it
@Shared
public interface StateCss extends CssResource {
String selected();
}
public interface BookshelfCss extends StateCss { String header(); /* ... */ }
public interface NavCss extends StateCss { String item(); }
public interface BookshelfResources extends ClientBundle {
@Source("bookshelf.gss") BookshelfCss css();
@Source("nav.gss") NavCss nav();
}
@Shared on the supertype makes css().selected() and nav().selected() return the same text; the run printed css.selected=EFLPHAC-a-f nav.selected=EFLPHAC-a-f shared=true.
Each file that extends the shared type still has to contain a rule for it: the GSS compiler rejects an accessor without a class (The following method [focused()] doesn't match a constant nor a style class. You could fix that by adding .focused {}), so nav.gss in the example has .selected .item { ... }.
@Import and @ImportedWithPrefix solve the opposite problem: you need to write a descendant selector across two unrelated types whose accessors have the same name.
The imported type gets a prefix (the annotation value, or the simple name of the type) and its classes are written .<prefix>-<name> in the importing file:
@ImportedWithPrefix("tree")
public interface TreeCss extends CssResource { String widget(); }
@ImportedWithPrefix("cb")
public interface CbCss extends CssResource { String widget(); }
public interface MyCss extends CssResource { String other(); }
public interface Resources extends ClientBundle {
@Import({TreeCss.class, CbCss.class})
@Source("my.gss")
MyCss my();
}
.tree-widget .cb-widget { color: red; }
.other { margin: 0; }
The run compiled this to .GO1C0CD-e-a-probe-gss-TreeCss-widget .GO1C0CD-a-a-probe-gss-CbCss-widget{color:red} (pretty style), keeping each class in its own scope.
It is an error to import two types with the same prefix.
ui:style has an import attribute that does the same for a template (it takes a space-separated list of type names, and it exists in the generator source, UiBinderParser, although the guide does not describe it).
Two corrections to the guide’s text emerged:
-
@Sharednames are shared inside oneClientBundle. TheCssResourcethat UiBinder generates for aui:stylelives in its own generated bundle, and in the probe aui:stylewhose type extended a@Sharedinterface did not get the same name as the same accessor of a hand-written bundle (global=BKJK6UC-a-a template=BKJK6UC-b-b equal=falsein GSS;GAWRW50BKIagainstGAWRW50BJIin legacy CSS). Put shared state classes in one hand-written bundle and apply them from Java. -
Annotation values that are class literals (
@Import({TreeCss.class})) are read from compiled classes, so the compile classpath must containtarget/classesand not only the source folders; with only sources the compiler failed withAnnotation error: expected class java.lang.Class, got nulland then aNullPointerExceptioninInlineClientBundleGenerator. Maven and Gradle put the classes first, so this appears with hand-made command lines.
Names that are not Java identifiers, strictness and external classes
Three annotations and one at-rule decide what happens with class names that do not map one-to-one to accessors:
| Tool | Effect |
|---|---|
|
Maps a class that is not a Java identifier ( |
|
Allows class selectors without accessors ("not recommended for new code"); useful while moving old CSS in |
|
Leaves named classes unobfuscated. The accessor of an external class returns the original name; classes you name there do not need an accessor |
|
A deprecated no-op: strict mode is the default |
In legacy CSS @external takes selectors and wildcards: @external legacyBanner;, @external gwt-*;.
In GSS the names have no leading dot and a wildcard must be quoted: @external legacyBanner; and @external 'gwt-*'; (a dot gave Parse error ... Encountered " "." ". "").
Both forms were compiled, and in both the unobfuscated name appeared verbatim: the probe printed legacyBanner=legacyBanner.
// the accessor of an @external class returns the original name
String legacyBanner(); // "legacyBanner" in the output
The legacy CSS directives
Legacy CssResource syntax (the default in 2.13.1) is a CSS file with GWT at-rules.
The Developer Guide lists the features; here they all appear in one Bookshelf file that I compiled with CssResource.style set to pretty and also left obfuscated.
@def coverWidth 96px;
@def accent #1a5fb4;
@eval lowStockColor probe.client.Palette.lowStockColor();
@url spineUrl spineData;
@external legacyBanner;
.card {
width: coverWidth;
border: 1px solid accent;
margin-left: 8px;
float: left;
}
.title { font-weight: bold; }
.low-stock { color: lowStockColor; }
.legacyBanner .title { text-decoration: underline; }
.spine { background-image: spineUrl; }
@sprite .star {
gwt-image: "starIcon";
display: inline-block;
}
@noflip {
.pin { left: 4px; }
}
@if (probe.client.Palette.compact()) {
.card { padding: 2px; }
}
@if user.agent safari {
.title { -webkit-font-smoothing: antialiased; }
} @else {
.title { font-smooth: always; }
}
The bundle supplies starIcon (an ImageResource) and spineData (a DataResource), and the interface has accessors card(), title(), @ClassName("low-stock") lowStock(), star(), spine(), pin(), legacyBanner() and int coverWidth().
The output of getText() (pretty style, data URLs shortened) was:
/* CssDef */
/* CssDef */
/* CssEval */
/* CssUrl */
/* @external legacyBanner; */
.GCWXV1BCHI-probe-client-Res-BookCss-card {
width : 96px;
border : 1px solid #1a5fb4;
margin-left : 8px;
float : left;
}
.GCWXV1BCOI-probe-client-Res-BookCss-title {
font-weight : bold;
-webkit-font-smoothing : antialiased;
}
.GCWXV1BCKI-probe-client-Res-BookCss-low-stock {
color : #c01c28;
}
.legacyBanner .GCWXV1BCOI-probe-client-Res-BookCss-title {
text-decoration : underline;
}
.GCWXV1BCMI-probe-client-Res-BookCss-spine {
background-image : url('data:image/png;base64,...');
}
.GCWXV1BCNI-probe-client-Res-BookCss-star {
height : 16px;
width : 16px;
overflow : hidden;
background : url("data:image/png;base64,...") -0px -0px no-repeat;
display : inline-block;
}
/*@noflip { */
.GCWXV1BCLI-probe-client-Res-BookCss-pin {
left : 4px;
}
/*} @noflip */
/* @if (probe.client.Palette.compact()) { */
.GCWXV1BCHI-probe-client-Res-BookCss-card {
padding : 2px;
}
/* } */
Reading the output tells how each directive behaves:
| Directive | Behaviour |
|---|---|
|
A constant, replaced where used. A numeric single-value |
|
A static Java method evaluated when the stylesheet is injected, or at compile time when the compiler can evaluate it. |
|
Calls a chain of zero-argument methods on the bundle, such as the width of an |
|
Passes a non-standard value through unchanged; backslash and double quote must be escaped |
|
Replaces the name with |
|
Expands to the size, |
|
Evaluated at run time in a static context, unless the compiler can fold it. The probe’s |
|
Evaluated at compile time per permutation, so the |
|
Selectors not to obfuscate (see above) |
|
Protects its rules from RTL flipping (see "Right-to-left") |
The guide’s remarks about merging and minification describe the optimiser: comments and empty rules are removed, identical selectors and identical property sets can be merged unless that would reorder properties that conflict.
In the observed output the second .card rule (the @if block with padding) stayed separate, which is what the ordering rule predicts for a rule that could not be moved past the first.
For old CSS only, com.google.gwt.resources.css.InterfaceGenerator writes a CssResource interface from a style sheet:
java -cp gwt-user.jar:gwt-dev.jar com.google.gwt.resources.css.InterfaceGenerator -addPackageHeader -typeName com.example.bookshelf.client.style.NavCss -css nav.gss.
The flag in the guide, -standalone, still works as an alias; the usage text of 2.13.1 lists -[no]addPackageHeader.
Run on a GSS file it produced String item(); and String selected(); (the interface it prints is package-private, with @ClassName("gwt-Button") String gwtButton(); for external classes, and it renders a numeric @def as a String accessor), so review its output before using it.
GSS
GSS is the second syntax understood by CssResource, and it is built on Google’s open-source Closure Stylesheets.
It extends CSS3 syntax with constants (@def), mixins (@defmixin, @mixin), functions (mult and divide were compiled here, concat is a GWT addition), conditionals and loops.
The migration article says that all of Google’s own applications have used GSS instead of CSS since mid 2015.
GWT 2.7 introduced it as experimental; GWT 2.8.0 declared GSS no longer experimental and deprecated the old CssResource syntax, while keeping CSS as the default "for easier and smoother migration".
In 2.13.1 the default is still CSS: GSS is opt-in with a configuration property, and the Developer Guide (updated for GSS) and the migration article encourage the move.
<!-- in the application's *.gwt.xml -->
<set-configuration-property name="CssResource.enableGss" value="true"/>
<set-configuration-property name="CssResource.gssDefaultInUiBinder" value="true"/>
<!-- conversionMode is off by default; set it to strict only while migrating (see below) -->
Source: GWT — GSS Migration Guide
| Property | Default | Effect |
|---|---|---|
|
|
Every |
|
|
|
|
|
Makes GSS the default for every |
What differs from legacy CSS
The Developer Guide page "GSS vs CSS syntax" lists the differences; all of them below were compiled and the result printed.
| Feature | Legacy CSS | GSS |
|---|---|---|
Constants |
|
|
Constant expressions |
none |
|
Runtime substitution |
|
|
Data resources |
|
|
Sprites |
|
a normal rule with the property |
Conditionals on a binding property |
|
|
Boolean configuration property |
|
|
Runtime condition |
|
|
External classes |
|
|
RTL protection |
|
|
Mixins, functions, loops |
none |
|
A GSS file of the Bookshelf probe, with every GSS construct of the table, compiled and ran in the same way:
@def COVER_WIDTH 96px;
@def ACCENT #1a5fb4;
@def LOW_COLOR eval("probe.client.Palette.lowStockColor()");
@def SPINE_URL resourceUrl("spineData");
@external legacyBanner;
@defmixin box(BORDER_COLOR) {
border: 1px solid BORDER_COLOR;
border-radius: 4px;
}
.card {
width: COVER_WIDTH;
@mixin box(ACCENT);
margin-left: 8px;
float: left;
}
.title { font-weight: bold; }
.low-stock { color: LOW_COLOR; }
.spine { background-image: SPINE_URL; }
.star {
gwt-sprite: "starIcon";
display: inline-block;
}
.pin2 {
/* @noflip */
left: 4px;
padding-left: 3px;
}
@if (eval("probe.client.Palette.compact()")) {
.card { padding: 2px; }
}
@if (is("safari")) {
.title { -webkit-font-smoothing: antialiased; }
} @else {
.title { font-smooth: always; }
}
The text the GSS generator wrote, with the same pretty style (data URLs shortened):
.GO1C0CD-b-c-probe-gss-GRes-BookCss-card{width:96px;border:1px solid #1a5fb4;border-radius:4px;margin-left:8px;float:left}
.GO1C0CD-b-i-probe-gss-GRes-BookCss-title{font-weight:bold}
.GO1C0CD-b-e-probe-gss-GRes-BookCss-low-stock{color:#c01c28}
.GO1C0CD-b-g-probe-gss-GRes-BookCss-spine{background-image:url(data:image/png;base64,...)}
.GO1C0CD-b-h-probe-gss-GRes-BookCss-star{height:16px;width:16px;overflow:hidden;background:url(data:image/png;base64,...) -0px -0px no-repeat;display:inline-block}
.GO1C0CD-b-c-probe-gss-GRes-BookCss-card{padding:2px}
.GO1C0CD-b-i-probe-gss-GRes-BookCss-title{-webkit-font-smoothing:antialiased}
(The lines are shown on separate rows for readability; GSS minifies the whole text onto one line.)
Modern CSS in GSS
GSS is parsed by a CSS3-era parser, not by the browser, so a selector or value the parser does not know is a build error. The probe tried the CSS that the page on variables and media queries (CSS variables and media queries) and the layout panels use:
| Construct | Result | Detail |
|---|---|---|
|
accepted |
|
|
accepted |
values the parser does not interpret are passed through |
|
accepted |
a class inside |
|
error |
the allow-list is the supported way to admit a new at-rule |
custom properties ( |
parse error |
|
CSS nesting ( |
parse error |
flatten the rules; GSS has its own mixins |
|
parse error |
the parser stops at the |
Because of those limits, a design system written in modern CSS (or output of Sass, see "Related pages") should stay a plain style sheet loaded with a link, while CssResource styles the parts that are written for GWT and need obfuscation, typed access or per-permutation rules.
Migrating from legacy CSS to GSS
The migration article offers two paths (a one-step migration for a small application, a gradual migration for a large one) and a conversion tool. The order below follows both, with the additions that the probes showed to be necessary.
Step by step
-
Upgrade to a GWT release with GSS (2.8 or later) and make sure the host page uses a doctype (see the pitfalls on quirks mode).
-
Add
CssResource.enableGss=trueandCssResource.conversionMode=strictto the module. From now on the compiler converts every remaining.cssfile in memory, so the application keeps building while you move files. -
Convert a folder with
Css2Gss(commands below), which writes a.gssfile next to every.cssfile that does not have one. Its classpath needsgwt-devas well asgwt-user: withgwt-user.jaralone the tool failed withNoClassDefFoundError: com/google/gwt/core/ext/TreeLogger. -
Point
@Sourceat the.gssfile, or leave@Source("book.css")as it is: withenableGsstrue the compiler looks for a.gssfile of the same base name first, so a converted file replaces the.csswithout touching Java code (checked: both files present, conversion off, the.gsswas used). If one file in a@Sourcelist has no.gsscounterpart the compiler falls back to the originals, because a list must not mix the two syntaxes (Only either css files or gss files are supported on one interface). -
Review and fix the converted files (see "What the converter does"), build, and look at the page.
-
When no
.cssfile is referenced any more, delete the.cssfiles and setCssResource.conversionModetooff(the default; remove the line). SetCssResource.gssDefaultInUiBindertotrueonce everyui:styleblock is GSS.
# Convert one file; the GSS goes to standard output
java -cp {classpath} com.google.gwt.resources.converter.Css2Gss src/main/resources/book.css
# Convert every .css under a directory next to the originals, recursively
java -cp {classpath} com.google.gwt.resources.converter.Css2Gss -r src/main/resources
# A file that uses @def constants defined elsewhere needs a scope
java -cp {classpath} com.google.gwt.resources.converter.Css2Gss -scope colors.css,sizes.css book.css
Here {classpath} stands for gwt-user-2.13.1.jar, gwt-dev-2.13.1.jar and their dependencies (the output of mvn dependency:build-classpath for the client module works).
The options printed by the tool in 2.13.1 are -r (recursive), -condition list_of_condition (variables that are configuration properties and should not be turned into is() calls) and -scope list_of_files.
A file that already has a .gss neighbour is skipped (GSS file already exists - will not convert).
What the converter does
The converter wrote this GSS for the legacy book.css shown earlier (blank lines removed):
@def COVER_WIDTH 96px;
@def ACCENT #1a5fb4;
@def LOW_STOCK_COLOR eval('probe.client.Palette.lowStockColor()');
@def SPINE_URL resourceUrl("spineData");
@external legacyBanner;
.card { width: COVER_WIDTH; border: 1px solid ACCENT; margin-left: 8px; float: left; }
.star { gwt-sprite: "starIcon"; display: inline-block; }
.pin { /* @noflip */ left: 4px; }
@if (eval('probe.client.Palette.compact()')) { .card { padding: 2px; } }
@if (is("user.agent", "safari")) { .title { -webkit-font-smoothing: antialiased; } }
@else { .title { font-smooth: always; } }
| Legacy | Converted |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Known limits, from the article and the probes:
-
comments are lost: the old parser does not hand them to the converter, so re-add them by hand
-
a CSS hack such as
*zoom: 1is dropped with a warning ([WARN] Line 1 column 20: encountered "*". Was expecting one of: "}" ";" <IDENT>) and the build continues, so search the log for those warnings -
a file that uses a
@defdefined in another file fails ("refuses to convert if it encounters an undefined variable"): pass-scope -
the converter only supports strict mode and refuses broken CSS;
conversionMode=lenientexists in the compiler to repair invalid CSS "of the whole application at once", but the article calls strict "the mode you want to be using" -
the compiler’s own comment warns that automatic conversion "will be removed in the next version of GWT": treat
strictandlenientas a bridge
Configuration combinations
Many error messages come from invalid combinations of the three properties. All rows below were produced by compiling the Bookshelf card template (a ui:style with a type and a @def) with the settings shown.
enableGss / conversionMode / gssDefaultInUiBinder |
ui:style |
Result |
|---|---|---|
false / off / false |
CSS body, no attribute |
Works (the default) |
false / off / false |
|
|
false / off / true |
no attribute |
|
true / off / false |
CSS body, no attribute |
|
true / off / false |
|
Works |
true / off / false |
|
|
true / strict / false |
CSS body, no attribute |
Works (converted in memory) |
true / off / true |
GSS body, no attribute |
Works |
An important consequence is visible in the SDK itself: NativeHorizontalScrollbar, used by DataGrid, CellTable and CustomScrollPanel, has a legacy CSS ui:style inside its template.
With enableGss true and conversionMode left at off, a module that created a DataGrid failed with Errors in 'gen/com/google/gwt/user/client/ui/NativeHorizontalScrollbar_NativeHorizontalScrollbarUiBinderImpl.java' and the "referencing css files" message.
Setting conversionMode to strict fixed it (this is why the Bookshelf module of the earlier pages carries strict), and so did gssDefaultInUiBinder = true: with off and that property a module with 19 widget classes (DataGrid, CellTable, CellList, CellBrowser, SimplePager, Tree, MenuBar, DateBox, DatePicker, SuggestBox, RichTextArea, SplitLayoutPanel, TabLayoutPanel, StackLayoutPanel, TabPanel, DisclosurePanel, CustomScrollPanel, DialogBox, PopupPanel) compiled, so the SDK’s inline CSS is also valid GSS.
The pure-GSS configuration of the worked example is therefore enableGss true, gssDefaultInUiBinder true and no conversionMode.
Styles in UiBinder
ui:style writes CSS next to the markup that uses it; the UiBinder page explains the element and its attributes (field, src, type, gss, and the generator-only import).
What matters for styling is scope: the compiler turns each ui:style block into a CssResource of its own, so .card in one template never collides with .card in another.
The class names are written in attribute values as {style.card}, and a typo is a compile error rather than a silent no-op.
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:with field="res" type="com.example.bookshelf.client.style.BookshelfResources"/>
<ui:style type="com.example.bookshelf.client.style.BookCard.Style">
@def GAP 8px;
.card { margin: GAP; padding: GAP; border: 1px solid #c0bfbc; position: relative; }
.title { font-weight: bold; }
.selected { border-color: #1a5fb4; }
</ui:style>
<g:FlowPanel styleName="{style.card}">
<g:SimplePanel styleName="{res.css.cover}"/>
<g:Label ui:field="title" styleName="{style.title}"/>
<g:Label ui:field="copies"/>
<g:Button ui:field="lend" text="Lend"/>
</g:FlowPanel>
</ui:UiBinder>
-
typenames aCssResourcesubtype (a nested interface of the owner class) so Java can read the names through a@UiField Style style;; the interface must not require a name the CSS does not define (aghost()accessor gaveghost: Fix by adding .ghost{}) -
styleName="{style.card}"replaces the widget’s class attribute;addStyleNames="{style.card} {style.hot}"(plural) adds to it. Use the second one on a widget whose theme class you want to keep -
the global bundle is declared with
ui:withand read as{res.css.cover}(thecss()accessor), so one set of rules is shared by many templates, and the template’s own block holds what only that template needs -
a misspelt name fails the build:
{style.cardd}gaveCould not find no-arg method named cardd in type probe.ub.BookCard.Style -
ui:styleis injected automatically when the template is first created, so you do not callensureInjected()on it; a global bundle that you declared by hand still needs it, and the example does so in the constructor
The Java side reads the scoped names through the typed field:
public class BookCard extends Composite {
interface Binder extends UiBinder<Widget, BookCard> {}
/** Scoped to this template: the three names are obfuscated for this template only. */
interface Style extends CssResource {
String card();
String title();
String selected();
}
private static final Binder BINDER = GWT.create(Binder.class);
private static final BookshelfResources RES = BookshelfResources.INSTANCE;
@UiField Style style;
@UiField Label title;
@UiField Label copies;
@UiField Button lend;
public BookCard(String bookTitle, int copiesLeft) {
RES.css().ensureInjected();
RES.nav().ensureInjected();
initWidget(BINDER.createAndBindUi(this));
title.setText(bookTitle);
copies.setText(copiesLeft + " copies");
if (copiesLeft < 3) {
copies.addStyleName(RES.css().lowStock());
}
lend.setEnabled(copiesLeft > 0);
}
public void setSelected(boolean selected) {
setStyleName(style.selected(), selected);
}
}
title.setText(...) and copies.setText(...) put the book data in as text, never as HTML; do the same for any value that comes from the server (the SafeHtml & XSS page covers the rest).
Right-to-left (RTL)
CssResource can generate the right-to-left variant of a style sheet at compile time.
The variant is chosen at run time by LocaleInfo.getCurrentLocale().isRTL(), so it follows the module’s locale property (a language such as Arabic, ar, makes isRTL() true); the Internationalization page covers how locales are added.
Adding a right-to-left locale doubles the permutations: the RTL probe compiled four permutations instead of two.
The Developer Guide lists the transformations: left and right swap, so do the values left/right of clear, float, text-align and the page-break-* properties; background positions in percent are mirrored; four-value margin, padding and border-* shorthands are reversed; xyz-right properties become xyz-left; direction is flipped on the body selector only; and ne-resize-style cursors are mirrored.
A pixel-based background-position: 4px 10px is not flipped; for old browsers the guide suggests a sprite with left/top instead.
Both syntaxes did the same in the probes:
| Syntax | Default locale | Locale ar (isRTL() true) |
|---|---|---|
Legacy CSS |
|
|
GSS |
|
|
To keep a rule as written, mark it noflip; the way depends on the syntax.
The probe used a class that must always sit at the left edge:
/* legacy CSS: a block */
@noflip {
.pin { left: 4px; }
}
/* GSS: a comment before the declaration */
.pin2 {
/* @noflip */
left: 4px;
padding-left: 3px;
}
Under ar, the GSS output was .pin2{left:4px;padding-right:3px}: left stayed, the neighbouring padding-left flipped.
Two ways to get it wrong, both silent unless you read the output:
-
the comment must sit before the declaration; placed before the rule (
/* @noflip */ .pin3 { left: 4px; }) the GSS pass still flipped it (pin3{right:4px}) -
the legacy block form is not GSS:
@noflip { ... }in a.gssfile isunknown @ rule
ImageResource can be mirrored too, with @ImageOptions(flipRtl = true) on the bundle method (the ClientBundle & Images page covers it).
The theme modules have an RTL version (DarkRTL, StandardRTL, CleanRTL, ChromeRTL); the probe linked gwt/dark/dark_rtl.css for DarkRTL, while the plain Dark module links dark.css.
Bookshelf: the stylesheet of the catalog
The running Bookshelf scenario of this guide gets its look in four files, all compiled and run in a scratch module (com.example.Shelf) with the Clean theme: a global GSS bundle, a small shared-state file, a UiBinder BookCard with a scoped style, and the module settings.
<module rename-to="shelf">
<inherits name="com.google.gwt.user.User"/>
<inherits name="com.google.gwt.user.theme.clean.Clean"/>
<source path="bookshelf/client/style"/>
<entry-point class="com.example.bookshelf.client.style.ShelfEntry"/>
<extend-property name="locale" values="ar"/> <!-- right-to-left, for the demonstration -->
<set-configuration-property name="CssResource.enableGss" value="true"/>
<set-configuration-property name="CssResource.gssDefaultInUiBinder" value="true"/>
</module>
/* bookshelf.gss: leaves the theme's own class names untouched so that they can be overridden */
@external 'gwt-*';
@def ACCENT eval("com.example.bookshelf.client.style.Theme.accent()");
@def LOW_STOCK #b03a2e;
@def COVER_WIDTH 72px;
@defmixin rounded(RADIUS) {
border-radius: RADIUS;
}
.header {
background-color: ACCENT;
color: #fff;
padding: 0.6em 1em;
}
.gwt-Button {
background-color: ACCENT;
color: #fff;
border: 0;
@mixin rounded(4px);
}
.gwt-Button[disabled] {
opacity: 0.5;
}
.selected {
background-color: #dbe9fa;
}
.low-stock {
color: LOW_STOCK;
font-weight: bold;
}
.cover {
gwt-sprite: "coverPlaceholder";
display: inline-block;
float: left;
margin-right: 8px;
}
.ribbon {
position: absolute;
/* @noflip */
left: 0;
top: 0;
}
@if (eval("com.example.bookshelf.client.style.Theme.compact()")) {
.header { padding: 0.2em 0.5em; }
}
/* nav.gss: the second stylesheet of the bundle; .selected is shared with BookshelfCss */
.item {
display: block;
padding: 0.5em 1em;
cursor: pointer;
}
.selected .item {
font-weight: bold;
}
The Java types are the BookshelfCss, BookshelfResources and BookCard shown earlier, plus the shared and navigation types and a small class that supplies the run-time accent colour:
@Shared
public interface StateCss extends CssResource {
String selected();
}
public interface NavCss extends StateCss {
String item();
}
public final class Theme {
private Theme() {}
public static String accent() {
return "#1a5fb4";
}
public static boolean compact() {
return false;
}
}
The module showed what the page claims. This is the output of the entry point after a deferred command (so that the injected style sheet is in place), in the default locale:
locale=default rtl=false
COVER_WIDTH=72
lowStock=EFLPHAC-a-d
css.selected=EFLPHAC-a-f nav.selected=EFLPHAC-a-f shared=true
button bg=rgb(26, 95, 180) radius=4px disabled-opacity=0.5
<div class="EFLPHAC-c-a EFLPHAC-c-b"><div class="EFLPHAC-a-b"></div><div class="EFLPHAC-c-c">Emma</div><div class="gwt-Label EFLPHAC-a-d">1 copies</div><button type="button" class="gwt-Button">Lend</button></div>
Reading it:
-
COVER_WIDTH=72is the numeric constant read from Java -
the card element has two classes (
-c-aiscard,-c-bthe template’sselected), the title-c-c, while the globallowStockis-a-d: three scopes in one page, no collision -
the
Buttonclass is stillgwt-Button(@external 'gwt-*'), so the Clean rules and the override both match, and because the override is injected later in the document at the same specificity it wins: the button background changed from the theme’s transparent torgb(26, 95, 180), and the disabled button gotopacity 0.5from.gwt-Button[disabled] -
in the right-to-left locale the same bundle printed
float:right;margin-left:8pxfor.coverand keptposition:absolute;left:0for.ribbon, as/* @noflip */requires -
in Super Dev Mode (port 9911) editing
bookshelf.gssand requesting a recompile changed the result in about 0.3 seconds (@mixin rounded(10px)gaveradius=10pxafter the reload) and the class names were readable (stablestyle)
Security notes
-
Never build CSS from user input.
StyleInjector.inject(String)andCssResourcetext go into astyleelement. A value from a request must not become part of that text, because a declaration likebackground: url(...)can load a remote resource and make the browser send data, and CSS can be used to extract attribute values. Set user-controlled values on elements with the typedStyleAPI only after validating them, and select user-controlled states with class names from accessors -
GSS
eval(...)(legacy@eval) runs a static Java method of your own code; the expression is part of the source, not of the request. Keep those methods free of side effects and sensitive state -
obfuscation is not a security control: class names are short, but the style sheet is public
-
StyleInjectorwrites an inlinestyleelement, so a Content-Security-Policy with a strictstyle-srcmust allow it; the source ofStyleInjectorhas no nonce support. Inlined images aredata:URLs, soimg-srcmust allowdata:. The SafeHtml & XSS and XSRF, RPC & Server Security pages cover CSP in the whole application -
when you keep a style sheet outside GWT (a
linkto a third-party theme), its origin is part of your trust boundary; use Subresource Integrity if it is on a CDN
Pitfalls
Each row was reproduced with the 2.13.1 compiler (module -strict), unless it says it comes from the documentation.
| Message or symptom | Cause | Fix |
|---|---|---|
|
a class in the file has no accessor |
add |
|
an accessor has no class in the file |
add |
|
the same, in GSS; it also hits |
add an empty rule for the shared class |
|
a dashed class name and an accessor |
|
|
|
convert the file; for the SDK set |
|
|
enable GSS or drop the attribute |
|
|
enable GSS first |
|
a |
convert the block to GSS and remove the attribute |
|
|
one syntax per accessor |
|
a legacy block such as |
use |
|
a legacy |
|
|
|
|
|
a constant is used but not defined in GSS; legacy CSS accepted the same file and left |
define it, or re-check the spelling |
|
a lower-case |
use upper-case names |
|
|
fix the name; the compiler is protecting you |
a |
the comment applies to the next declaration |
put it before the declaration |
a |
UiBinder generates its own bundle per template |
apply shared state classes from one hand-written bundle |
|
|
run the compiler with |
the override style has no effect right after |
|
read styles in a deferred command |
|
only |
add |
styles are lost, or |
the migration article: GSS uses upper and lower case in class names and "running your gwt host page in quirks mode will make GSS fail" (not reproduced; the observed names such as |
use |
Where older material is outdated
-
The Developer Guide, CSS page. It lists three themes (Standard, Chrome and Dark) and says new applications use Standard; the source has a fourth, Clean, which the archetype uses, and the guide’s text predates Clean. It also calls the
linkand<stylesheet>approaches "Deprecated". -
The Developer Guide, ClientBundle page. Its CssResource section is the legacy syntax:
ie6,ie8,operain@if user.agentblocks and-standalonefor the interface generator are historical, and the guide’s remark that the obfuscation prefix is an "Adler32 checksum of the source css file expressed in base36 as a prefix (7 chars)" does not match the names observed (ten characters such asGCWXV1BCHIin legacy mode; in GSS a seven-character prefix such asGO1C0CD, derived from the CssResource types in the source, plus a counter such asb-c). -
The book (2007). Chapter 3 teaches module
<stylesheet>, aDashboard.cssnext to the module,setStyleNameand the default widget style names (it points to a web page that listed the standard names). That still works, and the naming conventions it recommends (a primary style name per widget and dependent names for state) are the ones the SDK uses today; but there was noCssResource,ClientBundle, UiBinderui:style, GSS, themes or RTL flipping. -
Experimental flags. Articles and posts that mention
CssResource.legacy(2.7.0) describe the experimental stage;CssResource.conversionModereplaced it.
What changed recently
| Version | Change |
|---|---|
2.7.0 |
Experimental GSS: |
2.8.0 (Beta1) |
GSS no longer experimental; the old |
2.8.0 RC1 and RC3 |
|
2.13.1 |
Default is still CSS ( |
Related pages
-
Widgets covers the style-name methods in detail,
ensureDebugIdand the widget gallery whose class names you style -
UiBinder covers
ui:style,ui:with,ui:imageandui:dataand the generated classes -
Panels and layout covers the layout panels, which are styled with the CSS in this page
-
Project structure and modules covers
<stylesheet>,<inherits>and<set-configuration-property> -
Deferred binding and generators explains why
CssResourceis a generator and why@ifonuser.agentproduces different text per permutation -
Development mode and debugging covers the Super Dev Mode used to edit styles
-
ClientBundle & Images, Internationalization, Accessibility & ARIA and Testing, and the accessibility guide for the contrast and focus rules that a custom theme has to honour
-
outside GWT: HTML & CSS: selectors and specificity, CSS variables and media queries and theming for the CSS that GWT passes through; Sass and Sass compilation if you write the design system in Sass: compile it to plain CSS and load it with a
linkor a<stylesheet>, and useCssResourcefor the GWT-owned part, because a.cssfile that goes through aCssResourcemust be parsed by the GWT CSS3 parser
References
-
GWT in Action (Manning): first edition, GWT 1.3/1.4, consulted for the outdated-material notes only