Maintain custom CSS styles on components through feature updates, etc

Just wanted to share a nice way to (as far as I can tell) consistently adhere styles on a component with custom CSS. So far, it hasn't steered me wrong in adhering through feature updates in Retool.

Here's a quick step by step:

  1. Drag and drop any component onto your main canvas in Retool.
  2. Give it a name (or don't, but you probably should). For example, the first button you drop onto a new app will be called button1. Change it to whatever is fitting for your scenario. For this example we'll call it customCssButton.
  3. Go to the section in the GUI where you can write your custom css styles.
  4. Here's the secret -- you use an attribute selector to target your css. Start by targeting your component by ID (the name you gave your component i.e. customCssButton) like this:
  [id*="customCssButton"] {
      /* ...your styles here */
  }
  1. Using the syntax [id="idName"] is what is referred to as an attribute selector. We then add the asterisk symbol '*' so that we can get matches anywhere in the value name. This is what is referred to as substring matching, which there are many different uses for. You can get more detailed information on attribute selectors and substring matching at the following links:

MDN - Attribute Selectors
WW3 - CSS Attribute substring Matching
StackOverflow - Detailed Example Usage

  • Placing the asterisk after your 'id' attribute selector will find id names that match anywhere in the substring value of that id. So, using [id*="customCssButton"] will find any and all id names that contain 'customCssButton' within their name, aka their attribute value.

There should absolutely always be an id that contains the exact name you designated for your component. In this case, a button. And if you gave your component a clear and unique name to your app, there almost certainly will not be another html element that contains the exact name and casing that you designated for any of your components.

This should ensure that your custom css will always adhere to your styling conventions. You can do anything here from styling more deeply nested elements within your component, adding complex css like animations to your component, etc.

Please let me know if you have done something similar or if you need a more clear explanation! I hope some people in the community find it useful :pray:

2 Likes

Hey @AJVancattenburch,

This is super helpful, thank you!

How would you approach formatting for sub-elements of components, e.g. the toolbar buttons within a table?

1 Like

Hey @MiguelOrtiz! Especially once you are at the component level of styling by substring matching your id i.e. [id*="yourComponentId"], there are typically other distinctive ID or class attributes and/or their values that are also very far more distinct and obviously named than some of the more illogically named classes.

For context, below is an example of what I mean between the differences of a conventionally named id/class, and one that is non-conventional/illogical that would likely change with feature updates, etc. in Retool:

Conventionally named id which, in this case, is also the component ID I am targeting. You can see that I named this component stageAvgsReportChart:

Unconventionally named class name that will likely change at some point during a future Retool feature update:

Referencing the highlighted attribute values above, we target class or ID names that are conventionally named (first image) instead of ones that are a random string of uppercased letters, lowercased letters, and/or special/numeric characters.

Below, I will attach 3 more images. Image 1 and image 2 will be of the opened 'elements' tab within my developer tools where I highlight the CSS targets for an animation on a bar graph that animates the bars up and applies a hue rotation every time the 'Time Between Stages' tab is opened within this app. Image 3 will be of the custom css style targeting so it can reliably reference unchanging nested element styles within my component:

  1. Image 1 (hitting first level of targeting after hitting my component ID):

  • In the above image, 2 levels above the <g class="points"> element, you will notice an element <g class="barlayer mlayer" />.
  • I will first target the svg element, then target the <g class="barlayer mlayer" /> element using the ^ symbol instead of the * symbol -- where the difference is that ^ matches any substring value that follows the one you provide.
    • In image 3, you will see I target `[class^="bar"], which essentially means any attribute values that start with 'bar' will be targeted and styled.
  1. Image 2 (hitting the deeper nested points where all <g class="point" /> elements are stored, i.e. each 'bar' in chart component is an instance of a point or <g class="point" />):

  1. Image 3 (how I target this using custom CSS while ensuring I am targeting classes/id's that will remain consistent and not change with updates):

  • Observing in the above image where I have highlighted my custom CSS with a red border, you can see how I'm hitting id's, classes, and element tags that are extremely unlikely to change, if ever.
  • We first target our component by matching part of our ID string, as there is a chance Retool may change some wording around our ID target, but the name we give our component should always be included, Retool may just change/remove having 'retool-widget-' before the name, '--0' after the name, etc. etc.
  • Then, we target the svg within our chart component, which all Plotly charts are SVGs in the Retool chart component.
  • We then target the directly following child of the SVG <g /> element using the > combinator.

**

  • For our following target, we are using what was described below image 1 or the carot symbol ^ for substring matching any class attribute with a value that starts with the string value of 'bar...'. This will target the <g class="barlayer /> element. This puts us 4 layers above each <path /> element (aka each bar on the graph) that we are going to target and style with our final selector.
  • Finally, we hit our final target which are all path elements nested under the <g class="barlayer /> element.

This is a complex example that allows us to apply custom styling and in this case, even animations, to deeply nested html within Retool apps that targets only non-changing, or highly unlikely to change, CSS selectors to target that should always adhere through Retool feature updates.

I hope this explanation made sense for you to be able to apply for your own use cases! I will attach a final video link here that shows the final result of this working example, as well. I hope this helps!

2 Likes

My apologies, looking back on your question I may have gotten a little excited about tapping into SVG chart animations using the same strategy to target bars on a graph. I should have provided a short solution pertaining to what you were curious about; formatting sub-element toolbar buttons on a table component after targeting the component ID with CSS.

I have applied that before. I could provide a quick screenshot to this thread tomorrow of how I've used this logic to style a table's toolbar / toolbar buttons! :pray:

1 Like

Haha @AJVancattenburch, much appreciated. I think your post explains the concept really well, and should apply equally to toolbar / toolbar buttons. I have applied CSS to the bar and buttons, but on in the way you do it, and I'm always concerned the classes will change and mess up with the style....

1 Like

Totally understandable! :slight_smile: that's why I will always try to use a conventionally named id or class attribute. If there simply just isn't one available, I'll target a direct child using the > combinator, an element at the same level using the ~ combinator, or most frequently -- we can target with attribute selectors by substring matching consistent parts of the value. Then target the desired div, span, button, etc. directly. Like in the following example:

Say we have a component named myCustomTable and want to target and style each of the buttons on the bottom right of the table footer toolbar. Here is a good example below, where there is not much to work with in terms of a reliable attributes to target that would adhere through feature updates in Retool:

  • I'm sure you notice there isn't much to go off here; lots of nonsense class names. That's when attribute selectors and substring matching are also super handy for reliable targeting! As we can even target custom attributes like the style, aria-label, data-test-id, or literally any attribute available.

:x: Bad example of targeting the buttons using attributes and values that will probably change:

div.chad08 div.rwhlSb div.iZPcsP button {
  background: red;
}
  • Although the above CSS works, it likely won't for long targeting classes with values like 'rwhlSb' or 'iZPcsP'. Instead, we target it like this:

:white_check_mark: Good example targeting conventionally named parts of element attribute substrings that are likely to remain the same:

[aria-label*="toolbar"] [data-subcomponent-id*="Toolbar"] [type="button"] {
  background: red;
}

These will both style the same buttons on a table's toolbar, but the bottom strategy will adhere through updates. I hope this was a better example geared toward what you were curious about! :slight_smile:

1 Like

Worth noting, the second good example marked with the ':white_check_mark:' icon is something you could literally copy/paste into any project and it would work. Targeting like we do in the bad example marked with the ':x:' icon will be a single app use case, and the styling won't stick for long.

So you can even save snippets of CSS that should be able to dynamically theme any component in any project consistently.