Open MarcSkovMadsen opened 11 months ago
In https://discourse.holoviz.org/t/how-to-change-font-in-pn-pane-str/5788 Jan asks for help styling. The solution is
import panel as pn
pn.extension()
STR_STYLE_SHEET = """
:host {
--bokeh-mono-font: Times;
}
"""
pn.pane.Str(
"This is a raw string which \n"
"will not be formatted in any way \n"
"except for the applied style.",
styles={"font-size": "20pt", "line-height": "120%", "letter-spacing": "-1px"},
stylesheets=[STR_STYLE_SHEET],
).servable()
This is an example where
styles
and one via stylesheets
. There is no way to way to understand what and how to set this without inspecting the css. font-family
is set via the --mono-font
css variable which again is set via the --bokeh-mono-font
css variable. Why is there not just one variable?--font-size
is there but not used.--line-height
or --letter-spacing
css variable. Thus its a mix of strategies without any logic.I also want to empathize that styling components is a fairly tedious and time-consuming process.
I too thought your Example issue 1
should have worked, but ended up having to inject stylesheets on each component.
Fyi one solution for this is to subclass the components you want to style
import panel as pn
slider_css = """
/* Show a border when hovering the area the handle responds to */
:host {
--handle-width: 15px;
--handle-height: 25px;
--slider-size: 25px;
}
"""
class MySlider(pn.widgets.FloatSlider):
stylesheets = [slider_css]
slider1 = MySlider(name="Number")
slider2 = pn.widgets.FloatSlider(name="Number")
col = pn.Column(slider1, slider2)
col.servable()
You can use MySlider throughout your app without having to pass in stylesheets all around. If you know you want to affect all sliders, you can assign the .stylesheets property of the FloatSlider directly.
Stumbled here trying to apply a CSS style block to my whole page, seems like it shouldn't be that hard?
On Discord, @maximlt suggested doing:
class MyDesign(Design):
modifiers = {
Viewable: {
'stylesheets': [Inherit, 'assets/myglobalstyle.css']
}
}
pn.config.design = MyDesign
Maybe this should be documented if it's the proper thing to do.
Sounds like a good idea to document. But be aware that if you want to modify a Bootstrap or Material design you probably need to extend their modifiers
.
Are there performance implications for this? I'm building an app with many widgets, and the DOM is cluttered with included stylesheets, and I'm guessing it's slow to render because of this.
In that thread, Philipp stated: """ I think unless you are using inline CSS you should not worry very much about performance. Stylesheets that are loaded from the server are cached in browser, so producing a single CSS file that is applied everywhere is okay to start with and can always be broken up later. So I'd say in your case I think a Design would be quite reasonable. """
Are there performance implications for this? I'm building an app with many widgets, and the DOM is cluttered with included stylesheets, and I'm guessing it's slow to render because of this.
I would be very surprised if this was the case unless you were injecting many stylesheets with thousands of lines into every single component. I guess I'd have to see exactly what you were doing though. Are these stylesheets inlined or loaded from the server?
After not managing to forward the css variables explicitly into the shadow DOM I went this route too. It feels a bit hacky but to @MarcSkovMadsen 's point, what worked for me was
my_template.design.modifiers[Viewable]["stylesheets"] += raw_css
I would be very surprised if this was the case unless you were injecting many stylesheets with thousands of lines into every single component. I guess I'd have to see exactly what you were doing though. Are these stylesheets inlined or loaded from the server?
Sorry I've been slow to get back to this. When I posted the comment above I wasn't yet using inline styles. Still, every element was including identical styles throughout the document:
At the time of my previous post, I was rendering ~120 EditableFloatSlider
and EditableRangeSlider
widgets each of which are comprised of multiple subwidgets which carry their own styles with them, along with some markdown, rows, dividers, columns, etc. Opening the developer console in FF to inspect the DOM took so long it was basically impossible to modify styles for the document.
At the moment, I am instead using ~120 FloatInput
widgets, and the performance is much better - presumably because they aren't made up of ~10 subwidgets that carry their own styles. I can now inspect the DOM (there's still a ~2s delay opening it) though, so that's an improvement but still not ideal.
More recently, I've worked on styling the UI, which meant adding styles
and stylesheets
to the widgets I'm using. I've noticed that since these changes, the performance of the UI is significantly worse.
@peytondmurray Not directly related to the issue, but if you have performance issues you could group some of those 120 widgets inside sub layouts (column, card, etc) and only render the section that is relevant.
I wanted to increase the font-size a bit for a mobile app. It led me to look at how css variables are used in Panel.
I believe that they are used in a fundamentally wrong way. I see this as a bug.
My understanding of css variables is that they are there to enable users to tweek the look at feel of components from outside the component/ shadowRoot. But in Panel I see them defined inside the component/ shadowRoot.
Please fix this and define the variables once on the template level. And make it such that they can be overridden once when using custom templates or using Panel with
index.html
+styles.css
files in pyscript.com. My request is really to adhere to the best practices from the mdn docs https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties.Example issue 1 - Cannot change the look and feel for all components inside a container
You cannot change the look and feel for all components inside a container.
Workaround
You will have to change the css variable on all the components
Alternatively you can hack your way around this using
!important
or*
instead of:host
. But this is really a hack. Not the right way to do it.Example 2 - Pyscript
When using Panel with pyscript I would expect it to be possible to define/ override css variables once via a
stylesheet.css
file that is imported in thehead
section. But this is not possible.This just shows how difficult it will be to integrate Panel into a larger app whether its pyscript, flask, django etc. It should be possible to let the data scientist create the data app. And the the web developer/ designer style the app from the outside via css variables.
Example 3 - Light/ Dark theme
We would really like to be able to quickly change between light and dark theme. Today we do this by reloading the page. But really it should be possible by just changing a class on the body element or a high level container. Alternatively replacing one .css file.
Best Practices for CSS Custom Properties
See https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties.
Documentation
When implemented the documentation below should be updated.
CSS Variables should be configured globally or on a container. Can also be configured on a component, but that should be a less used use case.
Additional Inspiration
I look to Shoelace and Fast.design to for reference implementations using Shadow dom.
Its worth looking into the way themes a defined in Shoelace. Not on the component. But inside classes. This makes it really easy to change the theme or apply themes to parts of a page.
Its also worth looking into using Constructable Stylesheets as Fast does. See https://web.dev/constructable-stylesheets/#:~:text=Constructable%20Stylesheets%20make%20it%20possible,Document%20easily%20and%20without%20duplication.