CSS guidelines for decoupled modules

The answer to this question does not point to any selector or css property that I am not aware of. It also doesn't dump some of the random css that makes this particular case work. ... From head to toe, I can imagine several ways to make this particular example work. I'm sure there are hundreds more.

What are the best practices for creating CSS so that different design elements are kept separate?

Explanation of what I mean:

I am a programmer with a clear design. When writing good code, I want to create classes / objects that are decoupled, which means there are no weird and unexpected interactions between them. I can mix and match my classes / objects freely and the results work well and are what you expect. All my attempts to learn / create CSS best practices don't work that well. I've been a .NET web developer for over 10 years now. For a long time, I've believed in semantic CSS. I loved csszengarden.com... I tried to learn OOCSS and SMACSS. Despite all of this, I can't seem to get my CSS to work the way I can get my code to work. I search the internet for CSS best practices and find things like naming, formatting, and a few tips and tricks. Never a deep understanding of how to create decoupled CSS. Perhaps this is simply not possible. I don't feel like it should be. I feel like it should be possible to create a design language for reusable elements that can be compiled.

Since all this is very abstract and difficult to discuss without an example. Here is an example of the problems I am facing. This is based on the loading situation, but I've simplified the styles. So please understand that the styles are as they are, because that is what makes sense for the rest of the site, and this example does not involve some trivial changes that make it work in this particular case.

Example:

The code for this is on jsbin .

I have a panel module with title and content. Usually the title contains h2 and one or more button actions:

Basic Module

Notice that equal padding around the title and action floats to the right. This design is responsive, and when the panel is narrow, actions should go below the title. Although there is actually a problem when this happens is that there is no space between the title and the button.

But actually a panel is a module that can have anything, its title. This should follow the OOCSS principle of "separation of containers and content". Therefore, it doesn't matter what we put in the panel title. We want it to work well.

Now, on a specific page, it makes sense to put a selection list in the panel header. As with Bootstrap, there are many styles related to forms, so we'll use those styles as well. The result looks something like this:

Module with Form in Header

Note that because the form group (for each hopper) has a bottom margin, there is now half the space at the bottom of the header (the bottom margin provides correct spacing on forms with multiple form groups). I agree with our designer that double space is wrong, it should be equal to the amount of space as the top (as in a simpler example). I found a good article on how to deal with this. However, the "best" option at the end (uses*:last-child

), which is the one I like, does not work here because the form is not the last element in the container because the action button should float below the select list when the window is small. Also, I feel that such situations can always arise. Note that in this case, when the window is small and the button floats below the selection, the spacing is good, as the field in the form group provides a gap between them.

Also, the developer says the button should be vertically aligned with the selection (looks better with loading because the inputs are the same height). There doesn't seem to be a way to accomplish something that isn't very specific to the particular combination of elements here or the particular page it appears on. That is, I cannot imagine what the best practice is for things like this to line up correctly.

The CSS for the above is too long to be included in this already long question, but check jsbin again .

Repeating the question:

Again, I'm not looking for a specific CSS that will fix this specific example. I want to know what best practices will allow me to create CSS for decoupled design elements that can be freely combined without constantly running into problems like above.

+3


source to share


2 answers


About CSS layout

Position, size of children should be the responsibility of parents

What are the best techniques for creating CSS, such as separating different design elements?

I gave this question a significant thought and I was glad I was sent a link to your article "CSS does not match" by a colleague, this is how I found this question.

Since CSS is intrinsically suitable for creating selectors in the global space, dividing decoupled modules into namespaces is a sound best practice.

You can optionally define base styles for a shared component class globally, but rely on namespace classes and / or identifiers to apply positional and dimension styles to components based on where they are included.

Do not apply positional or dimensional styles directly to any component container. All other styles are defined by the component in its namespace.

In other words, the position and size of the child component must carry the parent container.

Right:

/* A component namespace */
.my-component-A {
    padding: 10px;
    background-color: White;
}

/* A component styles its children as necessary */
.my-component-A > p {
    margin: 0 0 1em 0;
}

/* Position & dimension applied by parent (Page A) within its namespace */
.my-page-A .my-component-A { 
    float: left;
    margin: 0 10px 0 0;
}

/* Position & dimension applied by parent (Page B) within its namespace */
.my-page-B .my-component-A { 
    float: right;
    margin: 0 0 0 10px;
}

/* Position & dimension applied by parent (Component B) within its namespace */
.my-component-B .my-component-A {
    margin: 10px;
}

      



Wrong:

/* Position & dimension applied by component within its own namespace */
.my-component-A {
    float: left;
    margin: 0 10px 0 0;
    padding: 10px;
    background-color: White;
}

/* Position & dimension now have to be overridden by parent */
.my-component-B .my-component-A {
    float: none;
    margin: 10px;
}

      

By using the source or page namespace to apply position and dimension to child components, you create a system of fully combined decoupled modules. And because a component will never change its own size or position, the developer who maintains the parent component or page has the confidence that they are in control of the position of their children.

If you are consistent and willful, each component can be placed in any other component. And since it doesn't define its own size or position, it flows into the parent container with its default browser positional and sizing styles, or with the styles provided by your global reset.

You, as the developer who maintains the page or parent component, have complete freedom to change the layout of everything in that namespace without having to apply overrides. And if your team is on board with this best practice, you don't have to worry about styling changes made to a child component maintained by someone else, breaking the page or parent component layout that you are maintaining.

Each component is not position and dimension oriented, relying entirely on its parent component or page to determine its placement and size as needed.

In your example, we can apply this principle to create a form group component that can be positioned on the page and in other components at different depths, while at the same time giving up control of the layout in the page or parent component.

/* Component Base / generic reset */
.component {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* Use generic page and component class to target all child components at once */
.pagewrapper > .component {
  margin: 20px;
}

/* Component: Panel */
.component.panel {
  /* No positional styles defined on component container */
  border: 1px solid Black;
}
.component.panel .panel-header {
  /* `display: table` is a great legacy & cross-browser solution for vertical alignment */
  display: table;
  position: relative;
  overflow: auto;
  border-bottom: 1px solid Gray;
}
.component.panel .panel-header > * {
  /* set all immediate children as table-cells to get vertical-alignment */
  display: table-cell;
  padding: 10px;
  table-layout: auto;
  border-collapse: collapse;
  vertical-align: middle;
}
.component.panel .panel-header > .actions {
  width: 1%;
}
.component.panel .panel-header h2 {
  margin: 0;
  font-size: 25px;
}
.component.panel .panel-content {
  /* Exclude bottom padding and apply margin-bottom to immediate children instead */
  padding: 10px 10px 0;
}
.component.panel .panel-content > * {
  /* All immediate children of the container get a bottom margin */
  margin: 0 0 10px;
}

/* Component: Form Group */
.component.form-group {
  /* No positional styles defined on component container */
  padding: 10px;
  background-color: Beige;
}
.component.form-group label {
  display: block;
}

/* Component position and dimension are namespaced to the parent */
.pagewrapper.home > .component.form-group {
  /* Use child selector to limit scope of namespaced components where a component may exist multiple times as descendants of the same ancestor. */
  /* You may override any other styles such as background-colors */
  background-color: Lavender;
}
.component.panel .panel-header .component.form-group {
  /* Positional style is always applied in the context of a parent container… */
  margin: -10px;
}
.component.panel .panel-content .component.form-group {
  /* …because it may need different positioning even within the same parent component. */
  margin: 0 -10px;
}
.component.panel .panel-content .component.form-group:first-child {
  /* Strategically use pseudo-class selectors… */
  margin: -10px -10px 0;
}
.component.panel .panel-content .component.form-group + * {
  /* …and sibling selectors (and other selectors as warranted). */
  margin-top: 10px;
}
      

<main class="pagewrapper home" id="my-page-A">

  <!-- Position and dimension defined by page…
       `.pagewrapper > .component` -->
  <section class="component panel" id="my-panel-A">
    <div class="panel-header">
      <h2>Title</h2>
      <div class="actions">
        <button>Add</button>
      </div>
    </div>
    <div class="panel-content">
      <p>Content</p>
    </div>
  </section>

  <section class="component panel" id="my-panel-B">
    <div class="panel-header">

      <!-- Position and dimension defined by parent component…
           `.component.panel .panel-header .component.form-group` -->
      <div class="component form-group" id="my-form-group-A">
        <label>A Label</label>
        <select>
          <option>Something</option>
        </select>
      </div>
      
      <div class="actions">
        <button>Add</button>
      </div>
    </div>
    <div class="panel-content">
      <p>Content</p>
      <p>More content</p>
    </div>
  </section>

  <section class="component panel" id="my-panel-C">
    <div class="panel-header">
      <h2>Title</h2>
      <div class="actions">
        <button>Add</button>
      </div>
    </div>
    <div class="panel-content">
      <p>Content</p>
      
      <!-- Position and dimension defined by parent component…
           `.component.panel .panel-content .component.form-group` -->
      <div class="component form-group" id="my-form-group-B">
        <label>A Label</label>
        <select>
          <option>Something</option>
        </select>
      </div>

      <p>More content</p>
    </div>
  </section>

  <!-- Position and dimension defined by namespaced page…
       `.pagewrapper.home > .component.form-group` -->
  <div class="component form-group" id="my-form-group-C">
    <label>A Label</label>
    <select>
      <option>Something</option>
    </select>
  </div>

</main>
      

Run codeHide result


+1


source


CSS doesn't have to be separate. It is designed to give the parental cascade effect a child and can be powerful enough, reliable, and sustainable when used correctly. OOCSS is close to what you want, but not quite. With OOCSS, the intent is to put common attributes in a single class that can be reused for many cases.

I think you might need to learn Web Components and the use of Shadow DOM . It allows you to design your widgets to have their own styling separate from the main DOM page and further reduce the chance of unwanted styling.

EDITED: I know you said you were looking for best practices, not solutions. However, I felt I should provide some possible code examples as possible "best practices" for your situation. For more control, you should use more specific css selectors as they will be given a higher value when rendered. As a "best practices" for maximum control you should use containers with a unique identifier that can be used to target sections of your html with greater specificity (ie #mainContent

, #sideBar

, #myWidgetName

, etc.). You can then bind them to various CSS3 selectors for increased specificity and more control.

TL; DR: General CSS Recommendations



  • Start with basic styles (OOCSS is good for this)
  • Add a unique ID to your important container elements.
  • Use container IDs, classes, CSS3 selector, etc. target elements inside these containers and override the base styles as needed
  • Also keep in mind that where you place your rules is important (top of the stylesheet, bottom of the stylesheet, inline string, etc.).

/* ----- YOUR ORIGINAL STYLES -----*/

/* General form styles */
.form-group {/* like bootstrap, need space between form inputs */margin-bottom: 10px;}
.form-group label {display: block;}

/* Panel styles */
.panel {border: 1px solid black; background-color: white; margin: 20px;}
.panel-header {padding: 10px; border-bottom: 1px solid grey;}
.panel-header h2 {font-size: 25px; margin: 0px; display: block; float: left;}
.panel-header .form-group {float: left;}
.panel-header .actions {float: right; margin-top: 2px; /* nudge down to try and line up with title */}
.panel-content {padding: 10px;}

/* generic clear fix */
.clearfix:after {content: ""; display: table; clear: both;}




/* ----- SUGGESTED SOLUTION STYLES -----*/
.panel-header {position:relative;/* will assist with button alignment */}
.panel-header > *:nth-child(n) {
    margin-bottom: 0;/* zero out the bottom margin of all direct children of ".panel-header" */
}
@media (min-width: 768px) {
.panel-header .actions { /* now that the bottom margins are set this will position the button to be aligned to the bottom of the container */
    bottom: 10px;/*matches .panel-header margin*/
    float: none;
    position: absolute;
    right: 10px;/*matches .panel-header margin*/
}
}
      

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>
  <div class="panel">
    <div class="panel-header clearfix">
      <h2>Title</h2>
      <div class="actions">
        <button>Add</button>
      </div>
    </div>
    <div class="panel-content">
      Content
    </div>
  </div>
  
  <div class="panel">
    <div class="panel-header clearfix">
      <div class="form-group">
        <label>A Label</label>
        <select>
          <option>Something</option>
        </select>
      </div>
      <div class="actions">
        <button>Add</button>
      </div>
    </div>
    <div class="panel-content">
      Content
    </div>
  </div>
</body>
</html>
      

Run codeHide result


0


source







All Articles