Search code examples
htmlcssreactjscreate-react-appaframe

How can I override A-Frame CSS in an app built using create-react-app?


I'm building a mobile app with 2D & 3D elements.

2D elements use React, and I'm using create-react-app for the build/bundling etc.

3D elements use A-Frame.

For iOS, I need to obtain device orientation permissions, and I'm doing this using the standard A-Frame component: https://aframe.io/docs/1.3.0/components/device-orientation-permission-ui.html

However I want to change the appearance of the modal, by specifying my own CSS (as suggested on that page).

I have a problem that appears only in my production build.

I've written the CSS overrides that I want in my App.css. I build my production JS and CSS files.

They are included in index.html like this (inside the <head>)

<script defer="defer" src="/static/js/main.6b739253.js"></script>
<link href="/static/css/main.8198a130.css" rel="stylesheet">

However, on initialization, A-Frame adds its own CSS files to the end of the HTML header:

<style type="text/css" data-href="src/style/aframe.css">
...CSS here...
</style>

Because the A-Frame CSS appears at the end of the HTML header, it takes priority over my custom CSS, and I end up with the A-Frame styling for the modal, rather than my custom styling.

I've come up with a solution for this, but I think there's got to be a better one...

My solution: the following code at the top of my top-level React component, which explicitly removes the aframe.css styles from the HTML (I've also added the A-Frame styles that I don't want to override directly in my App.css, where I can have full control over what I do & don't include).

  useEffect(() => {
    var head = document.getElementsByTagName('HEAD')[0];

    for (var ii = 0; ii < head.children.length; ii++) {
      const child = head.children[ii];
      if (child.attributes['data-href'] && 
          child.attributes['data-href'].nodeValue === "src/style/aframe.css") {
        head.removeChild(child);
      }
    }
  }, [])

I'm looking for a better solution, where I don't have to duplicate large chunks of aframe.css in my own CSS file, and I can simply have the two CSS files loaded, but prioritized in a different order.

Ideally that solution would not require me to eject from create-react-app, which I have managed to avoid needing to do so far.


Solution

  • There is in fact a simple solution here.

    A CSS specifier that specifies an element selector + a class selector is more specific than a specifier that specifies a class selector only.

    https://www.w3schools.com/css/css_specificity.asp

    So the following CSS...

    button.a-dialog-allow-button {
      background-color: red;
    }
    

    will be more specific (and hence take precedence over) the A-Frame defaults which look like this:

    .a-dialog-allow-button {
      background-color: #00ceff;
    }
    

    Similarly you can override .a-modal with div.a-modal, .a-dialog with div.a-dialog etc.

    One thing to watch out for (this threw me when testing this)... Not all A-Frame modals use the a-modal and a-dialog classes.

    There are various other classes, used for specific modals - e.g. a-enter-vr-modal and a-orientation-modal (used for portrait / landscape orientation, rather than for device orientation permisions).

    So just if you see an A-Frame modal, and your changes don't seem to have worked, it may be because it is using a different set of modal classes from the ones you changed. Check the class names carefully!