Skip to main content

Custom CSS

Wave tries to handle all the styling for you to both save you time and provide the most optimal user experience. However, there might be cases, when you are not 100% happy with it:

  • You feel like some components are hard to use and you have some ideas about improvement. In this case, please don't hesitate to file a new feature request where we can further discuss and improve Wave for everybody.
  • You do not like alignment or some particular color - subjective case for which you need to use custom CSS.

It's recommended to keep CSS changes at minimum (ideally at 0) since the CSS selectors that you will need to use are not guaranteed to work across Wave updates. The HTML code in Wave is considered an "implementation detail" and is prone to change in order to deliver new features and bugfixes.

Loading external stylesheets

If the stylesheet you want to use is hosted somewhere, e.g. CDN, you need to use the stylesheets attribute. This is also handy for cases when you wish to load a well-known CSS lib like Bootstrap and use it within your ui.markup_card for example.['meta'] = ui.meta_card(

Using local CSS stylesheet

Prior to using your own CSS files, they need to be uploaded to Wave server. There are 2 ways of doing this:

from h2o_wave import Q, main, app, ui
import os

current_dir = os.path.dirname(os.path.realpath(__file__))

async def serve(q: Q):
# Upload the `styles.css` file to the Wave server
# and save its path into the `stylesheet_path` variable.
stylesheet_path, = await[os.path.join(current_dir, 'styles.css')])
# Use the uploaded file path in the `ui.stylesheet`.['meta'] = ui.meta_card(


Inline stylesheets

On the other hand, if all you want to do is tweak a few things here and there, the simpler solution would be to use the stylesheet attribute that accepts a CSS string.

# Use red colors for text in all paragraphs.
style = '''
p {
color: red;

page['meta'] = ui.meta_card(box='', stylesheet=ui.inline_stylesheet(style))

Targetting the desired element

CSS class names are autogenerated by our build tooling which makes them inappropriate for CSS selectors - they change with every build (new version) of Wave. Instead, you can notice there are data-test attributes all over the HTML markup. These are filled based on either card name (['<card-name>']) or on the name attribute in case of components. It's recommended to use the data-test attributes where possible instead.

Below, you can find a simple example on how to change the alignment of header title and subtitle.

from h2o_wave import main, app, Q, ui

async def serve(q: Q):
# Use "my_header" as card name.['my_header'] = ui.header_card(box='1 1 -1 1', title='Title', subtitle='Subtitle')
# Target all divs with data-test attribute that have value of "my_header",
# then target it's first immediate child div.['meta'] = ui.meta_card(box='', stylesheet=ui.inline_stylesheet('''
div[data-test="my_header"]>div {
flex-grow: 1;
justify-content: center;