How do you style Inputs when exporting via 'Download code'?

When saving a page to work outside Observable, using Export->Download Code, the content is largely unstyled (which is fine).

But I note that when using Inputs such as this:

viewof angle = Inputs.range()

not only is the input component shown but also angle = XX below it. I am struggling to find the id or selector to hide this via CSS. Does anyone know how to do this, or more generally any documentation how styling exported pages via CSS?

1 Like

It looks to me like the things you want to hide are classed observablehq--inspect; so I guess you could add the following CSS near the beginning of your index.html file:

<style>
  .observablehq--inspect {
    display: none;
  }
</style>
2 Likes

Thanks very much. That does the trick for this case.

More generally, is there anywhere this is documented, or is it just a case of browsing the observablehq source?

For example, if I wanted to style input elements such as the font used for table contents or range slider value, it is not clear where I should look to find out what class I should be styling (complicated because content is generated via JS).

1 Like

You can right-click an element and select “Inspect element” to view its source and CSS properties in the browser’s developer tools.

2 Likes

I don’t know of any documentation for this and I certainly used @mootari’s technique to find the relevant class. I do have a few things to add, though.

First off, I’m not sure how typical this usage case is. If you’re looking for control over the output, it probably makes more sense to write your own HTML and CSS and embed into that HTML file from your downloaded or online notebook. That gives you direct control over all the elements in the page. You can place controls side by side, lay DIVs on top of one another, or do just about anything you like.

Having said that (and, since you mention slider values), I often want to override the styles for Inputs.range, which I don’t particularly care for. The label is about a mile and a half from an oversized numeric input which all takes too much space from the tiny little slider. Styling it properly, though, is a bit tricky; it really depends on the label and types of numbers being displayed so I typically use Javascript to do that right in the notebook. For example:

n2 = {
  let n = Inputs.range([1, 9], { value: 5, step: 1, label: tex`n:` });
  d3.select(n).select("input[type=number]").style("width", "50px");
  d3.select(n).select("label").style("width", "20px");
  return n;
}

Here’s the output as opposed to the default:

2 Likes

Thanks very much for the pointers – very helpful.

Yes, if I were creating stand-alone pages I would probably adopt the process you suggest and embed cells where I need them in my own HTML. However, for this use case, I need to be able to provide a template Observable page for others to use so that when they export the doc, it “just works” when viewing on a local web sever. I can do this if I set the styles within the Observable page itself.

This is what I have so far, that appears to do a reasonable job at approximating the appearance of an Observable page when viewed stand-alone:

<style type="text/css">
  @import url('https://fonts.googleapis.com/css2?family=Source+Serif+Pro:wght@400;700&display=swap');

  body {
     font-family: 'Source Serif Pro', serif; 
     font-weight: 400;
     line-height: 1.6;
     color: #333;
     padding-left: 2em;
     padding-right: 2em;
  }

  p,div,ul,ol,li {
    max-width: 640px;
  } 

  form, table,tr,th,td {
    font-family: sans-serif !important;
    font-size: 10pt !important;
  }

  .observablehq--inspect {
    display: none;
  }
</style>
2 Likes

That’s looks really cool! I guess I didn’t realize your objective was to emulate the appearance of a notebook.

If you only want to hide inspector output in the downloaded version (without modifying the downloaded code), I suggest to match your changes to the environment that your notebook is running in. That way you can retain full functionality while in the Observable editor.

The following notebook might help with that (specifically the isHosted cell):

2 Likes

That’s really useful - thanks. That opens up the possibility of deliberately creating a different L&F for the downloaded version while leaving the Observable experience intact.

1 Like

I do like the idea of being ability to add ID or Class names to an INPUT.
So I posted an issue requesting it.

1 Like

The problem isn’t CSS or styles or class names; it’s the behavior of Inspector.into which is used by the index.html you get when you Download (export) code. Inspector.into will inspect everything defined in the notebook, and in the case of views, that means you’ll get inspectors for both the view (viewof angle) and the view’s value (angle).

So, the recommended way to fix this is to not use Inspector.into and instead define your own inspectors. Examples of this are given in the Observable Runtime README, but in short, you pass a function to runtime.module and return an inspector for anything you want to display on the page:

<!DOCTYPE html>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@observablehq/inspector@3/dist/inspector.css">
<body>
<script type="module">

import {Runtime, Inspector} from "https://cdn.jsdelivr.net/npm/@observablehq/runtime@4/dist/runtime.js";
import define from "https://api.observablehq.com/@observablehq/hello-world.js?v=3";

const runtime = new Runtime();
const main = runtime.module(define, name => {
  if (name === "hello") {
    const node = document.createElement("DIV");
    document.body.appendChild(node);
    return new Inspector(node);
  }
});

</script>

I’ll file an issue to change the code download so that it doesn’t inspect view values. Unfortunately I don’t think we can fix that with some magic inside of Inspector.into, but we can probably change the generated index.html so that it gives you something slightly more useful by default.

Filed issue: The “Download code” action should not inspect views and views’ values in the index.html · Issue #269 · observablehq/feedback · GitHub

4 Likes

That environment page has proven very useful. One thing that I would really like to be able to do is to detect the path of the ‘parent’ document if it has been forked (just as details.path does for the document itself). Is there any way of doing this?

Not really, no, as that information is only available through the internal API. If you only want to determine wether the notebook has been forked, you can use a crutch where you check the notebook’s author:

  • For a notebook hosted on Observable, check the notebook iframe’s subdomain:
    probably_a_fork = !document.location.hostname.match(/^jwolondon\./)
    
  • For downloaded notebooks you can probably fetch the package.json from the same directory, which contains information like the notebook name and author.

Otherwise I’d recommend to just store the original path as a cell value in the notebook.


Oof, I wasn’t aware of that.