How to Add Additional Information to an Attribute Selector via Nesting in SASS

How can I add additional information to an attribute selector via nesting in Sass?

The parent selector cannot be used that way, it only contains a reference to the previous selector. Sass has no way of targeting the contents of an attribute selector (at least, not outside of whatever string manipulation functions are currently available).

The best you can do is either write a custom function or use a content aware mixin to do the dirty work for you with variables. Here's what a mixin version might look like (assuming you want to keep using nesting):

@mixin my-sel($append: false) {
$class: if($append, selector-append(&, $append), &);
@at-root a[class^="#{$class}"], a[class*=" #{$class}"] {
@content;
}
}

utility-button {
@include my-sel {
/* some shared css rules */
}

@include my-sel(-one) {
/* some unique css rules */
}
@include my-sel(-two) {
/* some unique css rules */
}
}

Output:

a[class^="utility-button"], a[class*=" utility-button"] {
/* some shared css rules */
}
a[class^="utility-button-one"], a[class*=" utility-button-one"] {
/* some unique css rules */
}
a[class^="utility-button-two"], a[class*=" utility-button-two"] {
/* some unique css rules */
}

If you need to be able to nest this into other selectors, then it gets quite a lot more complicated:

@function class-to-attribute-selector($class) {
// the following line is completely optional, but you definitely need the @return
$class: if(str-index($class, '.') == 1, str-slice($class, 2), $class);
@return #{'[class^="#{$class}"]'}, #{'[class*=" #{$class}"]'};
}

@mixin class-sel {
$sel-list: &;

$new-sel: ();
@each $sel in $sel-list {
@if length($sel) > 1 {
$s: ();
@for $i from 1 to length($sel) {
$s: append($s, nth($sel, $i));
}
$class: nth($sel, length($sel));
$new-sel: append($new-sel, selector-nest($s, class-to-attribute-selector($class)));
} @else {
$new-sel: join($new-sel, class-to-attribute-selector(nth($sel, 1)));
}
}

@at-root #{$new-sel} {
@content;
}
}

.foo {
.utility-button {
@include class-sel {
/* some shared css rules */
}

&-one {
@include class-sel {
/* some unique css rules */
}
}
&-two {
@include class-sel {
/* some unique css rules */
}
}
}
}

.bar {
@include class-sel {
/* other rules */
}
}

Output:

.foo [class^="utility-button"], .foo [class*=" utility-button"] {
/* some shared css rules */
}
.foo [class^="utility-button-one"], .foo [class*=" utility-button-one"] {
/* some unique css rules */
}
.foo [class^="utility-button-two"], .foo [class*=" utility-button-two"] {
/* some unique css rules */
}

[class^="bar"], [class*=" bar"] {
/* other rules */
}

SASS and Data Attribute Selecting and Nesting

Prior to Sass 3.4, this is just not possible at all. The deal-breaking features here are the ability to store the current selector into a variable and the ability to split a string (though the later could be created via SassScript functions).

@mixin append-attr($x) {
$sel: &;
$collector: ();

@for $i from 1 through length($sel) {
$s: nth($sel, $i);
$last: nth($s, -1);
@if str-slice($last, -1) == "]" {
// if is just the bare attribute with no value, $offset will be -1, otherwise it will be -2
$offset: -1;
$current-x: $x;

@if str-slice($last, -2) == '"]' {
// this attribute already has a value, so we need to adjust the offset
$offset: -2;
} @else {
// no attribute value, so add the equals and quotes
$current-x: '="' + $x + '"';
}
$last: str-slice($last, 1, $offset - 1) + $current-x + str-slice($last, $offset);
$collector: append($collector, set-nth($s, -1, $last), comma);
} @else {
// following line will append $x to your non-attribute selector
$collector: append($collector, selector-append($s, $x), comma);
// the following line will not change your non-attribute selector at all
//$collector: append($collector, $s, comma);
}
}

@at-root #{$collector} {
@content;
}
}

Usage:

[data-product] {
color: white;

@include append-attr("red") {
color: red;

@include append-attr('-green') {
color: green;
}
}
}

[one], [two] {
color: orange;

@include append-attr('alpha') {
color: yellow;
}
}

[test], .test {
@include append-attr('-one') {
color: red;
}
}

.bar input[min] {
@include append-attr('5') {
background: yellow;
}
}

Output:

[data-product] {
color: white;
}
[data-product="red"] {
color: red;
}
[data-product="red-green"] {
color: green;
}

[one], [two] {
color: orange;
}
[one="alpha"], [two="alpha"] {
color: yellow;
}

[test="-one"], .test-one {
color: red;
}

.bar input[min="5"] {
background: yellow;
}

Related: Modifying the middle of a selector in Sass (adding/removing classes, etc.)

How to combine Sass Ampersand or nesting with attribute selector?

You don't need to add the square brackets. This is how it should look:

.class {
color: blue;
&:hover {
color: grey;
}
&:disabled {
color: red;
}
}

So I tested it by creating a html page with button which is disabled on click and changes to color red.

HTML:

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test</title>
<link rel="stylesheet" href="style.css">
</head>

<body>
<button id="test-btn" onclick="this.disabled=true">
Test Button
</button>
</body>

</html>

SCSS:

body {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}

#test-btn {

width: max-content;
padding: 1rem;

&:hover {
color: #000;
background-color: #fff;
border-color: #fff;
}

&:disabled {
color: #fff;
background-color: #f00;
border-color: #f00;
}
}

Here's the preview:
Click here

Nested css selectors by adding to class name in SASS

[class^="col"] {
[class*="-2"] {
width: 16.5%;
}
[class*="-4"] {
width: 33%;
}
[class*="-6"] {
width: 50%;
}
}

Will produce the following selectors:

[class^="col"] [class*="-2"] {}
[class^="col"] [class*="-4"] {}
[class^="col"] [class*="-6"] {}

Notice the space between each attribute selector. The selectors above will first search for and element that has a class attribute that starts with the .col class. They then find an element nested within the .col element with a class attribute that contains -2, -4 or -6 somewhere in the attribute.

By adding an ampersand & you can capture the current selector path. The following SCSS is the same as what you have now:

[class^="col"] {
& [class*="-2"] {
width: 16.5%;
}
}

Compiles to (space between attribute selectors):

[class^="col"] [class*="-2"] {}

Placing the ampersand immediately before the nested selector (like you have with first-child and last-child) gives a different result, the one you (likely) want:

[class^="col"] {
&[class*="-2"] {
width: 16.5%;
}
}

Compiles to (no space between attribute selectors):

[class^="col"][class*="-2"] {}

IMHO what you have currently is over engineered. I'd suggest something more straightforward and flexible. Use regular class selectors.

.col {    
&-2 { width: 16.5%; }
&-4 { width: 33%; }
&-6 { width: 50%; }
}

[class*="col-"] {
flex: 1;
margin: 0 8px;

&:first-child { margin-left: 0; }
&:last-child { margin-right: 0; }
}

See below Stack Snippet to see what the above is compiled to. Note that I used *= (asterisk equals) instead of ^= (caret equals) for class placement flexibility. Up to you if you want to enforce column classes to be the first class in the class attribute value.

https://codepen.io/anon/pen/yXveRq

.row {  display: flex;}
.col-2 { width: 16.5%; }.col-4 { width: 33%; }.col-6 { width: 50%; }
[class*="col-"] { flex: 1; margin: 0 8px;}
[class*="col-"]:first-child { margin-left: 0;}[class*="col-"]:last-child { margin-right: 0;}
<div class="row">  <div class="col-4">Col 1</div>  <div class="col-4">Col 2</div>  <div class="col-4">Col 3</div></div>

How do you define attribute selectors in SASS?

You can also nest it like this

input
&[type="submit"]
....
&[type="search"]
....

Simplifying sass attribute selectors

I've come to this idea:

@mixin attrVal($value) {
$attr: str-slice(#{&}, 2, -2); // $attr = "data-attr"
@at-root {
[#{$attr}="#{$value}"] {
@content;
}
}
}

[data-attr] {
@include attrVal('opt1') { width: 10px; }
@include attrVal('opt2') { width: 20px; }
@include attrVal('opt3') { width: 30px; }
}

Output (tested on sassmeister.com)

[data-attr="opt1"] { width: 10px; }
[data-attr="opt2"] { width: 20px; }
[data-attr="opt3"] { width: 30px; }

For this specific example there's no that huge simplification, but with this approach you're actually decoupling the attribute name from its value (in the aim of code reuse).

Multiple attribute selectors in SCSS

An @each loop would be another way to write this:

$directions: left right bottom top;
@each $i in $directions {
[no-padding*="#{$i}"] {
padding-#{$i}: 0 !important;
}
}

Target attribute values in SASS

This is close. You will have to restate the attribute name in your child selectors each time. Classes and IDs can get away with tacking on an ending to the parent class, but if you extend the same idea to your attribute your second selector would be [data-post][~="title"].

So:

[data-post] {
font-size: 10px;

&[data-post~="title"] {
font-size: 50px;
color: blue;
}
}

That should get you there. http://jsbin.com/kazusamore/edit?html,css,js,output



Related Topics



Leave a reply



Submit