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
"Position: Fixed" Not Woking When Parent Has the "Transform" CSS Property
Whitespace in Wrapped Form Element
Vertically Center HTML Element Within a Div of Dynamic Height
Why Is a Div with "Position: Absolute" Not by Default Relative to the Document
Cut Out Transparent Circle with CSS3
Style Every Thing Except First Child
How to Vertically Centralize a Bootstrap V4 Modal with CSS
Specifying Different Font-Sizes for Different Font-Families
Css3 Transition from Width Auto to "N" Px
Two Column Layout with One Having a Fixed Width in CSS
How to Create a Squeezed Box Using Only CSS
How to Center Text Inside a Li Element Inside an Unordered List
Excluding an Element from Nth-Child Pattern
Change Ie Background Color on Unopened, Focused Select Box
How to Merge .CSS Files with SASS (Or Other Tool)
Bootstrap 3 Panel Header with Buttons Wrong Position
How to Use Helvetica Neue Condensed Bold in CSS
Prefer Shrinking Over Growing in a Flex Container with Flex-Flow: Row Wrap