Styling GTK+ with CSS

div>

Carlos Garnacho shows how you can use CSS to style GTK+ apps.

 

What’s going on here?

Starting with GTK+ 3.0, we’re replacing the old theming layer with a more modern one. Designers will find it more powerful and immediate, and it’s more intuitive for the masses.

This new theming layer has been developed in a GTK+ repository, but is available in master now, and brings in features that are starting to be a must in modern toolkits:

  • Puts more into a language designers understand. While not everything is themeable through CSS, most of the styling information will be expressible in that language, which puts theming engines (and hence coders’ work) in a supporting role.
  • Gives developers the ability to seamlessly replicate the look of an UI element. When rendering, GTK+ widgets can assign different CSS classes that will be used for style matching, so other widgets can reuse the same set of classes and they’ll be themed the same.
  • Furthermore, the theming layer can be used outside of GTK+, as the style matching happens on abstract representations of a widget, which can be mimicked without even having a GtkWidget instance.
  • Makes embedding styles easier. Be it either an application with custom looks or a widget library offering a default style for its widgets, it is now possible to attach your style anywhere in the cascade.
  • Enables engines to implement implicit animations. State changes can be fully animated, so the theming engine code only has to retrieve the progress for a given state in order to modify its rendering. Complex widgets will be able to independently animate different regions.

CSS Selectors in GTK+

We’ll get into the details quickly and assume you know a bit of CSS. This is how the universal selector would look:

    * {
    }

This selector matches any GtkWidget. If you want to match a certain widget type, you need to specify its type name:

    GtkButton {
    }

This rule applies to any GtkButton, or any other child type of it. You may want, of course, to set a name to a widget so it can be styled differently:

    #some-widget-name,
    GtkLabel#title-label {
    }

As mentioned earlier, widgets may also use certain CSS classes in order to provide hinting about the style needed for a rendered element, so the following rules would respectively theme entries, buttons, and buttons in a spinbutton:

    .entry {
    }

    .button {
    }

    GtkSpinButton.button {
    }

Complex widgets could also assign regions to rendered elements, which can be matched in the CSS file as lowercase identifiers:

    GtkTreeView row {
    }

    GtkNotebook tab {
    }

But the widget may also provide further information to these regions, which can be matched in CSS through the :nth-child pseudo-class, so we can handle the even/odd coloring in treeview rows as follows:

    GtkTreeView row:nth-child(even) {
    }

    GtkTreeView row:nth-child(odd) {
    }

Even if the classes/regions added to a widget are arbitrary, GTK+ provides a set of these so they can be reused across widgets.Widget states are also matched in CSS as pseudo-classes:

    /* These two are equivalent */
    .button:prelight,
    .button:hover {
    }

    .entry:focused {
    }

And of course, you can combine all of this for more complex styling:

    GtkWindow > .entry:selected {
    }

    GtkTreeview column-header .button {
    }

    GtkWindow#my-window GtkCheckButton.check:prelight {
    }

Supported properties

GTK+ currently supports a subset of CSS properties:

      • background-color
      • background-image
      • color
      • border-color
      • border-image
      • border-radius
      • border-width
      • border-style
      • padding
      • margin
      • transition

The “background-color” “color” and “border-color” properties take colors (either symbolic, names, hex strings or rgb()/rgba() strings), and any combination of them through the supported functions:

    * {
      background-color: red;
      border-color: shade (mix (rgb (34, 255, 120), #fff, 0.5), 0.9);
    }

    .entry:selected {
      color: darker (@selected_fg_color);
      background-color: lighter (rgba (50%, 32%, 35%, 0.9));
    }

As in CSS3, you can use the “background-image” property to specify radial or linear gradients as an element background:

    .button {
      background-image: -gtk-gradient (linear,
                                       left top,
       left bottom,
       from (@bg_color),
       color-stop (0.5, darker (@bg_color)),
       to (@bg_color));
    }

    .entry {
      background-image: -gtk-gradient (radial,
                                       0.5 0.5 0.2,
       0.6 0.6 1,
       from (@bg_color),
       to (shade (@bg_color, 0.9)));
    }

In these gradients,you can define any number of color stops, and use any color definition, as in the previously mentioned properties.You can modify borders through several properties. The width is defined by “border-width”, which will modify the rendered frame’s width in pixels.

The “border-radius” property specifies the radius in pixels for the corners of a rendered frame. Usually, GtkWidgets will provide information about how a frame that’s being rendered visually connects with other elements in the UI, so the rounded corners only apply to the disconnected corners.

The “border-style” property accepts the “none”, “solid”, “inset” and “outset” keywords. You’d typically use “inset” and “outset” to simulate 3D-like effects on a button when pressed:

    .button {
      border-style: outset;
    }

    .button:active {
      border-style: inset;
    }

Implicit animations are also handled, although in a way different from standard CSS. In GTK+ CSS, only the animation length and timing function are specified, so for example a button can be told to animate from white to yellow like this:

    .button {
      background-color: white;
    }

    .button:hover {
      transition: 300ms ease-in-out;
      background-color: yellow;
    }

Putting it all together

The GTK+ you get by default is conservative—boring, if you will. Let’s take a well known GNOME app as an example—gedit:

gedit styled with default GTK

Ok, this is too boxy for our taste? Let’s try to fix that. First we create a ~/.config/gtk-3.0/gtk.css file and write:

  /* Make some things rounded */
  .notebook,
  .button,
  .entry {
    border-radius: 3;
    border-style: solid;
    border-width: 1;
  }

Things start to look a bit better now:used CSS to round some things

Now let’s use some soft gradients to make things look smoother:

  .menubar,
  .toolbar {
    border-width: 0;
    background-image: -gtk-gradient (linear,
                                     left top, left bottom,
                                     from (@bg_color),
                                     to (shade (@bg_color, 0.95)));
  }

  .button:hover,
  .menu:hover,
  .menubar:hover {
    background-image: -gtk-gradient (linear,
                                     left top, left bottom,
                                     from (shade (@selected_bg_color, 1.1)),
                                     color-stop (0.55, shade (@selected_bg_color, 1.0)),
                                     color-stop (0.55, shade (@selected_bg_color, 0.8)),
                                     to (shade (@selected_bg_color, 0.9)));
  }

Looks a bit better now? Hmm, but these scrollbars are still a bit ugly, let’s do something about them:

  /* Gradient for vertical scrollbars'
   * sliders, from left to right
   */
  .slider.vertical {
    background-image: -gtk-gradient (linear,
                                     left top, right top,
                                     from (mix (shade (@bg_color, 0.9), @selected_bg_color, 0.3)),
                                     to (mix (@bg_color, @selected_bg_color, 0.3)));
  }

  .slider.vertical:hover {
    background-image: -gtk-gradient (linear,
                                     left top, right top,
                                     from (shade (@selected_bg_color, 0.9)),
                                     to (@selected_bg_color));
  }

  /* Now horizontal scrollbars',
   * from top to bottom
   */
  .slider.horizontal {
    background-image: -gtk-gradient (linear,
                                     left top, left bottom,
                                     from (shade (@bg_color, 0.9)),
                                     to (@bg_color));
  }

  .slider.horizontal:hover {
    background-image: -gtk-gradient (linear,
                                     left top, left bottom,
                                     from (shade (@selected_bg_color, 0.9)),
                                     to (@selected_bg_color));
  }

  /* A soft radial pattern in the trough */
  .trough {
    background-image: -gtk-gradient (radial,
                                     center center, 0,
                                     center center, 1,
                                     from (@bg_color),
                                     to (shade (@bg_color, 0.8)));
  }

  GtkScrollbar.button {
    border-width: 0;

    /* Override any background with
     * a full transparent color
     */
    background-image: none;
    background-color: rgba (0, 0, 0, 0);
}

If we want the theme to be a bit more lively, we can also do:

  GtkButton:hover {
    transition: 100ms linear;
  }

  GtkCheckButton:active {
    transition: 250ms ease-in-out;
  }

So these states have a transition animation. After some other minor tweaks, we have something like this:improved look with gradients and other tweaks

With a relatively simple CSS file we’ve managed to improve the appearance of applications, but a lot more can be done in this brave new land.

If you are keeping an eye on gnome-shell development, either via compiling or through your favorite distro’s unstable packages, chances are you already have GTK+ 2.99.x, so do not hesitate to grab the resulting CSS file and start experimenting right away with it!

Carlos Garnacho is a GTK+ developer.

Discuss this story with other readers on the GNOME forums.

Posted on March 15, 2011, in March 2011. Bookmark the permalink. Leave a comment.

Leave a comment