Skip to content

Notebook cell styling (nbsphinx and MyST-NB) #2187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 36 commits into
base: main
Choose a base branch
from

Conversation

gabalafou
Copy link
Collaborator

@gabalafou gabalafou commented Apr 11, 2025

This is an implementation of Isabela's designs for notebook cell styling.

The pull request has a few parts:

  1. A bunch of CSS rules that replace style rules from nbsphinx and MyST-NB. In a previous iteration of this PR, I was overriding nbsphinx's and MyST-NB's rules, but this was complex and not long-term sustainable.
  2. An example notebook copied from nbsphinx's documentation that demonstrates lots of different code cells. This gives us some assurances that the styles are working as we intend and will help us spot errors in the future.
  3. Changes to the build process that enable us to swap out our CSS for the extensions' CSS during build time if the project using our theme depending on which extension the project is using, if it's using one.
  4. Some magic to allow us to build the theme docs using both nbsphinx and myst-nb at the same time. Boils down to designating a special suffix for MyST-NB (.mystnb.ipynb) and copying the source notebook file to path with that extension at the beginning of the build.

Known differences from the design

  • In the design, the labels for the code cells follow a format like "In 1" and "Out 1", but this PR drops the numbers.
  • In the design, the vertical bar was green. Here it is the theme blue.

How to test this pull request

To test on the preview build:

  1. Go to the code cells page in the preview build, as well as the code cells MyST-NB preview
  2. Inspect it visually.
  3. Also press the tab key to move about the page and see how the focus ring looks.
  4. Do the same for the pydata notebook
  5. Do it for both light and dark modes.

To test locally, build the docs locally and check the same pages.

@gabalafou gabalafou changed the title prettier Notebook cell styling Apr 11, 2025
@gabalafou gabalafou marked this pull request as ready for review April 25, 2025 17:11
Copy link
Contributor

@isabela-pf isabela-pf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks so good! I'm happy to be able to say nothing I saw on the preview page surprised me. Visually, this implementation follows the previously discussed designs and fits in with the visuals surrounding it. It was especially helpful to see it in use on so many notebook output types for review.

In the design, the labels for the code cells follow a format like "In 1" and "Out 1", but these labels are generated by nbsphinx so they are not easy to completely override with CSS. So in this pull request they follow a format like "In [1]" and "Out [1]" with brackets.

I consider this an acceptable break from the design, and really an oversight on my part to not catch the way nbsphinx was doing it in the first place. Thanks for calling this out for clarity regardless.

Other review notes:

  • This PR doesn't interrupt any responsive page behaviors, nor does it make any new responsive problems as far as I can tell.
  • I can navigate through the page contents with my keyboard the same way I could before. I'm not finding any changes in behavior with keyboard only-navigation.
  • I am not finding any changes in screen reader (VoiceOver) behavior with this PR. Everything appears to operate and read the same.
  • The visual changes this PR makes matches visuals in the existing theme. I wasn't concerned about this changing based on previous discussions, but I do want to vouch it was done properly.

Design-wise, I don't have any changes to request. Thanks for reaching out for a review.

@trallard trallard self-requested a review April 28, 2025 16:04
@gabalafou gabalafou changed the title Notebook cell styling Notebook cell styling (nbsphinx only) Apr 29, 2025
Copy link
Collaborator Author

@gabalafou gabalafou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finished self review. I feel pretty good about this.

One thing to consider is whether we should make it possible to disable these styles with a configuration variable.

- [email protected]
- [email protected]
- stylelint
- stylelint-scss
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed these lines because I was getting indecipherable error messages when trying to commit scss files with lint errors. After these changes, I got intelligible error messages.

"metadata": {
"nbsphinx": "hidden"
},
"source": [
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since most of this notebook is copied from the nbsphinx repo, I copied the MIT license from that repo and added it here in a cell that's hidden from nbsphinx—that way, the notice appears in the source code or any notebook application, but is not shown on the docs site.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These images are needed by the code-cells notebook

"mpg_df = pd.read_csv(mpg_csv, index_col=0)\n",
"mpg_df"
]
},
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this table because I wanted to see if the table-layout: auto rule would have any effect. (It doesn't.)

Comment on lines 61 to 89
// Override nbsphinx's colors for notebook cell prompts because they do not have
// sufficient contrast. Colors chosen from accessible-pygments
// a11y-high-contrast-{light,dark} themes.
// Override nbsphinx's styles with our own <pre> tag styles
div.nbinput.container div.input_area div[class*="highlight"] > pre,
div.nboutput.container div.output_area div[class*="highlight"] > pre {
@extend %pre-style;

// Notebook cell input line number. Replace nbsphinx's low contrast blue with
// higher contrast blues.
.nbinput.container .prompt pre {
html[data-theme="light"] & {
// Copied from accessible-pygments [a11y-high-contrast-light](https://github.com/Quansight-Labs/accessible-pygments/tree/main/a11y_pygments/a11y_high_contrast_light)
color: #005b82;
margin: 0;
}

html[data-theme="dark"] & {
// Copied from accessible-pygments [a11y-high-contrast-dark](https://github.com/Quansight-Labs/accessible-pygments/tree/main/a11y_pygments/a11y_high_contrast_dark)
color: #00e0e0;
// Last container styles
div.container.nblast {
margin-bottom: $top-and-bottom-container-space;

// Override nbsphinx's padding rule. Let the whitespace be controlled by the
// margin-bottom rule above. This is important because if we create
// whitespace with padding instead of margin, then we create a misalignment
// between the vertical left bar and the bottom of the notebook cell.
padding-bottom: 0;
}
}

// Notebook cell output line number. Replace nbsphinx's low contrast red with
// higher contrast red / orange.
.nboutput.container .prompt pre {
html[data-theme="light"] & {
// Copied from accessible-pygments [a11y-high-contrast-light](https://github.com/Quansight-Labs/accessible-pygments/tree/main/a11y_pygments/a11y_high_contrast_light)
color: #a12236;
}
// Nest under .bd-article-container so that selectors have higher specificity
// and can therefore override the rules in the nbphinx stylesheet.
.bd-article-container {
@include _nbsphinx-restyle;

.nboutput {
.output_area {
&.rendered_html,
.jp-RenderedHTMLCommon {
// pandas
table.dataframe {
@include table-colors;

tbody {
tr:hover {
background-color: var(--pst-color-table-row-hover-bg);
}
}
}
}

html[data-theme="dark"] & {
// Copied from accessible-pygments [a11y-high-contrast-dark](https://github.com/Quansight-Labs/accessible-pygments/tree/main/a11y_pygments/a11y_high_contrast_dark)
color: #ffa07a;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed these lines (61-89) because in Isabela's design, the cell label text, In [N] and Out [N], is just the same color as the surrounding documentation text.

tbody {
tr:hover {
background-color: var(--pst-color-table-row-hover-bg);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mostly yanked the above lines (12-36) and pasted them further down the file.

However, the margin rule was no longer needed because the other style rules added take care of focus ring issues.


div.input_area {
border: none; // border taken care of by <pre> styles elsewhere in the theme
overflow: visible;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all of the overflow: visible rules added in this PR were added to make the focus ring visible.

A safer, but more complex alternative would be to carefully add focus-ring-width padding to focusable elements. However, I have observed that whatever gets rendered into the input or output areas is usually rendered within some kind of container that has overflow: auto rules. In other words, I haven't seen any cases where the content rendered into the output or input areas overflows its parent (except for a few specific HTML widget cases that I handle separately).

Furthermore, I tried going down the route of adding padding to create space for the focus ring, and it really is quite a bit more complex. If you want the stuff in the input and output areas to be left-aligned with the labels (as in Isabela's designs), then you have to start carefully subtracting padding from parent elements wherever you add padding to child elements. Furthermore, as you will see further down, the way I fixed padding-related alignment issues was to set padding to zero using the same selectors that nbsphinx uses in its CSS. When you set padding to zero, you don't have to worry whether you're applying the rule to both parent and child. But when you add padding to create space in which to draw the focus ring, then you have to know whether the parent or child is getting focus, and the way this currently works is not consistent. Sometimes the parent element gets focus, for example the div.output_area containing a wide Pandas data table. Sometimes the child gets focus, for example the pre element inside of the div.output_area in the case of a plain text output.

So if you want to add focus-ring-width padding to div.output_area you have to make sure it does not contain any of those elements that are focusable and that also need focus-ring-width padding. That would mean a selector that looks like:

div.output_area:not(:has(pre, ...))

And I just think this is a very messy way to write CSS, and may not even work well across all browsers.

So I think it's worth taking the risk of setting overflow: visible in several places.

Comment on lines 141 to 147
// Override our own style rule in _math.scss
div.math {
display: block;
}

span.math {
display: inline;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will open a follow-up PR to better handle math content both within notebooks and outside of notebooks. That way we won't need to override our own rules.

&.rendered_html:not(:has(table.dataframe)),
// ipywidgets
.widget-subarea {
@include cell-output-background;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines (41-46) are just moved down, not actually deleted.

}
// Nest under .bd-article-container so that selectors have higher specificity
// and can therefore override the rules in the nbphinx stylesheet.
.bd-article-container {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these rules used to be nested under .bd-content. But as far as I can tell, notebook content will always be rendered within .bd-article-container, so this is the better selector to use for nesting.

@gabalafou gabalafou added impact: high Something that is relevant to nearly all users DO NOT MERGE There is something blocking merging this PR labels May 6, 2025
@gabalafou
Copy link
Collaborator Author

Labeling this as "do not merge" because it can be reviewed right now but it should not be merged until after 0.17 is released

@gabalafou gabalafou added this to the 0.18.0 milestone May 6, 2025
@gabalafou gabalafou added the tag: design Items related to design tasks or improvements label May 6, 2025
Copy link
Collaborator

@drammock drammock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is looking pretty good! nice work @isabela-pf and @gabalafou. Comments below on quirks that I noticed.

Comment on lines 142 to 144
"The standard error stream is highlighted and displayed just below the code cell.\n",
"The standard output stream comes afterwards (with no special highlighting).\n",
"Finally, the \"normal\" output is displayed."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as mentioned in the note box below, this doesn't necessarily reflect the order in the rendered doc (where stdout precedes stderr): https://pydata-sphinx-theme--2187.org.readthedocs.build/en/2187/examples/code-cells.html

Can we generalize the language here to say "normal output comes last" and update the note to say "when stdout/stderr are both present, which one comes first can vary by kernel"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! Added in the latest updates.

Comment on lines +275 to +318
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Image(url=\"https://jupyter.org/assets/homepage/main-logo.svg\")"
]
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems redundant; what does this cell demonstrate that is different from the one 2 cells before it (the first python logo, without embed=True)? Is .png vs .svg the point?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure myself, but yes I think that is the point.

In the latest update, I have added some markdown cells to label these code cells, which should help the reader to understand that the point of these code cells is in fact to png vs svg, passing the embed keyword argument versus not passing it.

"\n",
"\n",
"Latex(\n",
" r\"This is a \\LaTeX{} equation \"\n",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\\LaTeX{} isn't rendering correctly in the output

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Latex also not rendering on NBSphinx docs. I'll just remove the Latex reference.

Comment on lines +353 to +389
"<div class=\"alert alert-info\">\n",
"\n",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe better? Or was this meant to intentionally show a no-title alert box?

Suggested change
"<div class=\"alert alert-info\">\n",
"\n",
"<div class=\"alert alert-info\">\n",
"\n",
"Note\n",
"\n",

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I don't think the point is to show a no-title alert box. I don't think the point of the code-cells.ipynb notebook is to demo any markdown cells, only code cells (the nbsphinx docs have a separate page to demo markdown cells). So I added your suggestion in the latest update.

"outputs": [],
"source": [
"fig, ax = plt.subplots(figsize=[6, 3])\n",
"ax.plot([4, 9, 7, 20, 6, 33, 13, 23, 16, 62, 8]);"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this renders as a .png in the built docs, identical to the .png in the following cell's output.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a code cell above and checked locally that it makes this output SVG instead.

Comment on lines +537 to +599
"source": [
"a = {}\n",
"for name in [\"t\", \"q\"]:\n",
" da = xr.DataArray(\n",
" [\n",
" [[11, 12, 13], [21, 22, 23], [31, 32, 33]],\n",
" [[14, 15, 16], [24, 25, 26], [34, 35, 36]],\n",
" ],\n",
" coords={\"level\": [\"500\", \"700\"], \"x\": [1, 2, 3], \"y\": [4, 5, 6]},\n",
" name=name,\n",
" attrs={\"param\": name},\n",
" )\n",
" a[name] = da\n",
"\n",
"ds = xr.Dataset(a, attrs={\"title\": \"Demo dataset\", \"year\": 2024})\n",
"ds"
]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

output is illegible in dark mode, though for xarray content this is a known bug, not a regression (#2189)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed by my addition of :not:has(.xr-wrap) to this SCSS selector:

&.rendered_html:not(:has(table.dataframe, .xr-wrap)),

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Xarray in dark mode looks better now. Screenshot from preview build:

Comment on lines 706 to 769
"link = w.IntSlider(description=\"link\")\n",
"w.link((slider, \"value\"), (link, \"value\"))\n",
"jslink = w.IntSlider(description=\"jslink\")\n",
"w.jslink((slider, \"value\"), (jslink, \"value\"))\n",
"dlink = w.IntSlider(description=\"dlink\")\n",
"w.dlink((slider, \"value\"), (dlink, \"value\"))\n",
"jsdlink = w.IntSlider(description=\"jsdlink\")\n",
"w.jsdlink((slider, \"value\"), (jsdlink, \"value\"))\n",
"w.VBox([link, jslink, dlink, jsdlink])"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the labels "link", "dlink", "jslink", and "jsdlink" won't communicate the 4 scenarios clearly to many readers. Can we change to something like:

  • kernel vs javascript
  • bidirectional vs oneway

and also specify that what these are (bi/uni)directionally linking to is slider from the prior cell?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried to address this feedback in my latest updates!

Comment on lines +722 to +817
"source": [
"tabs = w.Tab()\n",
"for idx, obj in enumerate([df, fig, eq, i, md, slider]):\n",
" out = w.Output()\n",
" with out:\n",
" display(obj)\n",
" tabs.children += (out,)\n",
" tabs.set_title(idx, obj.__class__.__name__)\n",
"tabs"
]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. math isn't correctly rendered in dataframe colnames, nor in the dedicated math tab.

  2. Markdown text is nearly illegible in dark mode:

    Screenshot 2025-05-08 at 11-35-36 Code Cells — PyData Theme 0 16 2dev0 documentation

  3. contrast on widget slider handle border is pretty subtle

    Screenshot 2025-05-08 at 11-36-14 Code Cells — PyData Theme 0 16 2dev0 documentation

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Same issues on the corresponding nbsphinx code cells doc page. I have no idea how to fix this.
  2. Latest update should fix it
  3. Latest update should fix

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One way to fix the broken math in the tab would be just to remove it. I already tried moving the math tab to the first — in case being hidden off the DOM at page load was the reason it wasn't rendering. That didn't work.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Markdown issue fixed! Screenshot from preview build:

Comment on lines +182 to +208
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"raises-exception"
]
},
"outputs": [],
"source": [
"traceback # NOQA: F821"
]
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's odd that this cell gets the green "success" color for its left-border line. I would have expected orange/red. 🤔

Copy link
Collaborator Author

@gabalafou gabalafou May 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very good point. I wonder if we should use a different color that doesn't have semantic associations with success or failure. Maybe a gray border would be better? @isabela-pf, what do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a screenshot for reference:

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very good point. I wonder if we should use a different color that doesn't have semantic associations with success or failure. Maybe a gray border would be better? @isabela-pf, what do you think?

IMO, if this is an error or an exception this should use the failure (or equivalent) semantic colour. Or is there a reason you are suggesting using gray instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In consultation with Isabela, I updated this PR to use the primary (blue) color instead of green.

@trallard, to avoid scope creep, I would prefer to tackle the feature of a vertical bar that changes color (green for successfully executed code cells, red for failed) in a potential future PR. As far as I can tell, nbsphinx does not provide an easy way to distinguish success from error.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is fine, I had not realised this was changed to the primary colour, but if we were using semantic colours for one state, that should be consistent across various states.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know what you think about the primary, blue color bar. Maybe I've been looking at it too much, but I think it adds too much color to the page and distracts from the content. I'm thinking it should be gray, which would also allow it to be more consistent across all sites (even if they have customized the primary color to something other than blue). Also, if any site is using green for their primary color, then their users might also infer that the bar means "success" and be a little confused when it doesn't change color next to a cell that failed. Using gray would avoid that scenario.

@gabalafou
Copy link
Collaborator Author

@drammock, thank you so much for this feedback!

A lot of the things you caught have something to do with the original Code Cells notebook and nbsphinx itself. Almost all of the problems that you point out exist on the corresponding nbsphinx docs page for Code Cells (except for the dark mode stuff because the nbsphinx docs site does not support dark mode).

Some of these things I can fix on our docs by modifying the Code Cells notebook (such as changing the prose to make the document clearer). Some of these things will be beyond my ability to fix in this PR because they would require me to patch nbsphinx (or further upstream).

I think your comments raise an important point that I didn't fully appreciate when I copied this notebook from nbsphinx to our own documentation, which is that once it is on our docs site, we become in some way responsible for whatever shortcomings exist in the document. I was thinking about this page less in terms of documentation and more as just a place to check how our theme combines with nbsphinx. But maybe the docs site is not the appropriate place for that. Do you think it would make sense to create a separate site that isn't aimed at end users? Not a doc site but a demo site?

@drammock
Copy link
Collaborator

drammock commented May 9, 2025

I think docs/examples is a fine place for content like this (many users won't use nbsphinx, but for those who do, they should be able to easily see and assess how well we interoperate with it). I agree that by incorporating it here we take some degree of ownership, and I think that the right way forward is to modify the content to make clear what we can and can't fix (i.e., make the example a bit more self-documenting). In other words, more interleaved prose that points out the shortcomings and the barriers (big or small) to overcoming them.

Copy link
Collaborator Author

@gabalafou gabalafou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@drammock, I think I addressed all of your comments in my latest updates. I will need to go back through and give this another round of self review because I have made substantial changes since your last comments.

The big shift in the latest updates to this PR is that I am no longer overriding nbsphinx's style rules. Rather, I am completely replacing nbpshinx's stylesheet with one of our own.

This comes with a number of changes to the Python and build sides of the theme package.

Comment on lines +275 to +318
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Image(url=\"https://jupyter.org/assets/homepage/main-logo.svg\")"
]
},
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure myself, but yes I think that is the point.

In the latest update, I have added some markdown cells to label these code cells, which should help the reader to understand that the point of these code cells is in fact to png vs svg, passing the embed keyword argument versus not passing it.

"\n",
"\n",
"Latex(\n",
" r\"This is a \\LaTeX{} equation \"\n",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Latex also not rendering on NBSphinx docs. I'll just remove the Latex reference.

Comment on lines +353 to +389
"<div class=\"alert alert-info\">\n",
"\n",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I don't think the point is to show a no-title alert box. I don't think the point of the code-cells.ipynb notebook is to demo any markdown cells, only code cells (the nbsphinx docs have a separate page to demo markdown cells). So I added your suggestion in the latest update.

"outputs": [],
"source": [
"fig, ax = plt.subplots(figsize=[6, 3])\n",
"ax.plot([4, 9, 7, 20, 6, 33, 13, 23, 16, 62, 8]);"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a code cell above and checked locally that it makes this output SVG instead.

Comment on lines +182 to +208
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"raises-exception"
]
},
"outputs": [],
"source": [
"traceback # NOQA: F821"
]
},
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In consultation with Isabela, I updated this PR to use the primary (blue) color instead of green.

@trallard, to avoid scope creep, I would prefer to tackle the feature of a vertical bar that changes color (green for successfully executed code cells, red for failed) in a potential future PR. As far as I can tell, nbsphinx does not provide an easy way to distinguish success from error.

Comment on lines +537 to +599
"source": [
"a = {}\n",
"for name in [\"t\", \"q\"]:\n",
" da = xr.DataArray(\n",
" [\n",
" [[11, 12, 13], [21, 22, 23], [31, 32, 33]],\n",
" [[14, 15, 16], [24, 25, 26], [34, 35, 36]],\n",
" ],\n",
" coords={\"level\": [\"500\", \"700\"], \"x\": [1, 2, 3], \"y\": [4, 5, 6]},\n",
" name=name,\n",
" attrs={\"param\": name},\n",
" )\n",
" a[name] = da\n",
"\n",
"ds = xr.Dataset(a, attrs={\"title\": \"Demo dataset\", \"year\": 2024})\n",
"ds"
]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed by my addition of :not:has(.xr-wrap) to this SCSS selector:

&.rendered_html:not(:has(table.dataframe, .xr-wrap)),

Comment on lines +722 to +817
"source": [
"tabs = w.Tab()\n",
"for idx, obj in enumerate([df, fig, eq, i, md, slider]):\n",
" out = w.Output()\n",
" with out:\n",
" display(obj)\n",
" tabs.children += (out,)\n",
" tabs.set_title(idx, obj.__class__.__name__)\n",
"tabs"
]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Same issues on the corresponding nbsphinx code cells doc page. I have no idea how to fix this.
  2. Latest update should fix it
  3. Latest update should fix

Copy link
Collaborator Author

@gabalafou gabalafou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trallard oopsies you reviewed this PR as I was making changes on it today, before I had a chance to update the PR description.

There are a lot of changes that might not make sense without a good overview. I updated the PR description.

I also answered your inline comments.

Thanks so much!

Comment on lines 24 to 26
:focus-visible {
outline-color: #{map-deep-get($pst-semantic-colors, "accent", "light")};
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is within a mixin that's used to apply a light background to code cell outputs (such as ipywidgets) that we do not expect to have dark mode support built-in.

Comment on lines 70 to 72
// TODO: add to translations
content: "In:";
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But these in/out labels don't exist in JupyterLab or other notebook app interfaces that I have seen.

We are adding them in this theme as an accessibility affordance — primarily for cognitive accessibility, I would argue.

It's ultimately a rather small thing, but then again, big enough for us to add the labels in English...

So I think it would probably make sense to translate them, or rather, to make them translatable, and leave it up to the translators to make a judgment call about whether or not it would be appropriate to translate these strings given the context.

Comment on lines 214 to 216
/*
* Syntax highlighting for console/terminal output
*/
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 16 to 18
@import "abstracts/all";

// These files output *CSS* variables (not to be confused with SCSS variables)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No that should not be right. I tried to make sure that was not happening. Can you show me where that's happening?

Comment on lines 10 to 11
# This function will allow us to catch during development or CI if nbsphinx ever
# changes the name of the CSS file where it outputs its notebook styles.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No this function deletes their stylesheet. The other function replaces (sorta, it actually just updates the link to point to a different place).

The comment may not be in the best place but I'm trying to answer the question: why did I even bother to write this delete function? (cause it's pretty harmless to just leave nbpshinx's CSS file in the build directory)

return

nbsphinx_css = Path(app.builder.outdir) / "_static" / "nbsphinx-code-cells.css"
assert nbsphinx_css.exists()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure I can look into changing this. I was just following the patterns in other theme functions that also use plain asserts and are called within conf.py.

Should I just get rid of the assert and allow the unlink call to throw an error if the file does not exist? That makes more sense.

I guess there is a question of intent here. My main intent of the function is the assert. The delete is really secondary. I care more about catching if the file isn't there and being alerted by a change to nbsphinx then I do about deleting the file. Does that make sense?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking more about this, I do not think moving the colour variables definitions to abstract while keeping the supporting functions in variables makes sense conceptually. This seems backwards and goes against the established patterns in PST, where variables, pure functions, and mixins live.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! In my latest commits, I cut the mixin I had left in variables and pasted it in abstracts.

Just to make sure we're on the same page, my understanding is:

  • Sass files under abstracts should NOT output any CSS; they should only define variables, functions, mixins, etc.
  • Sass files under variables should output CSS, and it should mostly only be CSS variable definitions

Is that correct?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is my understanding, yes. I had initially put all the colours + functions in variables as that is where they were initially placed (back when they were all plain CSS variables, but in hindsight, I don't think this distinction was clear to me at the time and should have moved them to abstracts instead).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Sass files under abstracts should NOT output any CSS; they should only define variables, functions, mixins, etc.

  • Sass files under variables should output CSS, and it should mostly only be CSS variable definitions

Can this info be added to a new file src/pydata_sphinx_theme/assets/styles/README.md?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in latest updates!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, wait. A README.md in styles/. I put a separate README in each of the two folders, abstracts and variables. Okay, will combine them into a README for the styles folder.

Comment on lines 24 to 26
:focus-visible {
outline-color: #{map-deep-get($pst-semantic-colors, "accent", "light")};
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment to this effect? Otherwise, it might surprise others who look at the code later.

Comment on lines 70 to 72
// TODO: add to translations
content: "In:";
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But these in/out labels don't exist in JupyterLab or other notebook app interfaces that I have seen.

Mainly because these are not needed in an interactive context where the user gets to execute the cell and immediately see the output or result from such action. Or where this relationship is more evident.
In a static notebook version (like this in PST), there is no explicit interaction or relationship (especially if/when the output is suppressed for whatever reason), so these affordances are needed due to the lack of interactivity.

So I think it would probably make sense to translate them, or rather, to make them translatable, and leave it up to the translators to make a judgment call about whether or not it would be appropriate to translate these strings given the context.

Translators cannot decide whether these must be translated based on use/context. Once there are translation strings and added translations these will always be translated by default in all docs using PST for a given language (there is no way to turn a given translation on/off). So, a context-based translation cannot be done by translators or from a tooling POV.
So I do not think these should be translated and is in line with many discussions I have seen around whether all technical terms should always be translated. The overall consensus is that not all should be (the closest example I could find was the stdin/out one wich is generally not translated).

Comment on lines 214 to 216
/*
* Syntax highlighting for console/terminal output
*/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not checked nbsphinx rendering in a while, but do these meet WCAG AA contrast requirements? If not it would make sense to instead use colours more in line with our pygments style.

Comment on lines 10 to 11
# This function will allow us to catch during development or CI if nbsphinx ever
# changes the name of the CSS file where it outputs its notebook styles.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well it deletes it but the comment makes it seem this is purposely created to check (or test) if the name has changed on the the nbsphinx side of things

return

nbsphinx_css = Path(app.builder.outdir) / "_static" / "nbsphinx-code-cells.css"
assert nbsphinx_css.exists()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the main intent is to do a "check," then an appropriate error should be thrown if such a check is not successful.

@gabalafou gabalafou changed the title Notebook cell styling (nbsphinx only) Notebook cell styling (nbsphinx and MyST-NB) May 30, 2025
@gabalafou
Copy link
Collaborator Author

gabalafou commented May 30, 2025

Okay, I just pushed a lot of changes to this PR.

tl;dr-- this PR now includes MyST-NB; there's a list of tasks remaining to do

Since I labeled this PR as do-not-merge, and we are going to wait until the 0.18 release, I thought it would be better to put everything in one big PR.

By everything, I mean instead of handling nbsphinx and MyST-NB in separate PRs, I'm handling them now in this one PR.

Finding a way to build the example notebook with both nbsphinx and MyST-NB and putting the final result in our docs was one of the trickiest parts of this PR. I settled on a strategy that's a little bit hacky but has the virtue of being pretty straightforward:

  • At the start of the build, copy code-cells.ipynb to a file with a special .mystnb.ipynb suffix: code-cells-myst-nb.mystnb.ipynb
  • In examples/index.rst, create a toctree entry to that copied-in-the-future file: code-cells-myst-nb
  • Update conf.py so that it maps .mystnb.ipynb files to MyST-NB and .ipynb to nbsphinx
  • Update code-cells.ipynb with headings that are hidden from nbsphinx when converted by MyST-NB and from MyST-NB when converted with nbsphinx.

I decided on this strategy by working backwards from what I thought would be the best end user experience. In my view, the best experience would be two pages—one labeled nbsphinx (and generated with nbsphinx), the other labeled MyST-NB (and generated with MyST-NB)—both in the same documentation hierarchy. I also wanted to avoid creating a build process with multiple sphinx-build commands and a lot of copying files around between builds.

Some known issues / to-do's:

  • Do a line-by-line self-review of all of my latest changes
  • Fix all Axe-core issues found on these two pages
  • Investigate and possibly fix some rendering differences between nbsphinx and MyST-NB (for example, with MyST-NB the admonition is not rendering correctly).
  • Document known issues with these extensions at the top of the example notebook (for example, certain outputs get doubled in MyST-NB)
  • Add the example notebook docs page to our accessibility tests
  • Figure out if there's a way to use Pygments to color the ANSI-coded strings at the bottom of the example notebook. That way we avoid having to copy-paste a bunch of color mappings from nbsphinx and MyST-NB.

Future PRs:

  • Restructure the Examples section. I think the examples section looks a tad messy after this PR. However, I didn't want to squeeze a re-organization of the docs into this PR. In the future, I think it would be good for each extension to get its own page in the example section.

@gabalafou
Copy link
Collaborator Author

Latest commit updates the PR to copy the source file at build time, rather than rely on a symlink. I will go back and edit the description and my last comment to reflect these changes.

@gabalafou
Copy link
Collaborator Author

I should mention that there is another way to achieve the same goal of demonstrating both nbsphinx and MyST-NB:

  • We can create an environment variable like PST_WHICH_NB_EXTENSION, which is set either to "nbsphinx" or "myst_nb"
  • In conf.py we modify the extensions list based on the value of the environment variable
  • We create a new project in our Read the Docs account. The new project sets the environment variable like so: PST_WHICH_NB_EXTENSION=myst_nb
  • The site built with MyST-NB will be available at a URL like https://pydata-sphinx-theme.readthedocs.io/en/latest/myst-nb
  • We create a new doc page that contains a link to the site built with MyST-NB

The reason I didn't want to go with this approach is because it adds yet another multiplying factor to the documentation variations: software version (latest, stable, old versions) times language (just "en" currently, but could be more in the future) times notebook extension (myst-nb, or default nbsphinx).

@trallard
Copy link
Collaborator

trallard commented Jun 2, 2025

I should mention that there is another way to achieve the same goal of demonstrating both nbsphinx and MyST-NB:

I do not think that having one notebook rendered with nbsphinx or myst-nb while the rest of our docs is the same warrants the overheard of having two sites so let's leave it as is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DO NOT MERGE There is something blocking merging this PR impact: high Something that is relevant to nearly all users tag: design Items related to design tasks or improvements
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants