Ver índice de contenidos del libro

Capítulo 7. Reglas @ y directivas

Sass soporta todas las reglas @ (también llamadas "reglas at") definidas por CSS3. Además, Sass incluye varias reglas específicas llamadas directivas.

7.1. La regla @import

Sass mejora la regla @import de CSS para poder importar también archivos SCSS y Sass. Todos los archivos importados, independientemente de su tipo, acaban fusionándose antes de generar el archivo CSS final. Además, cualquier variable o mixin definidos en los archivos importados se pueden utilizar en la hoja de estilos principal.

Los archivos importados se buscan automáticamente en el directorio actual y en Rack, Rails y Merb también se buscan en el directorio de Sass. Utiliza la opción de configuración :load_paths para configurar todos los directorios adicionales en los que quieras buscar archivos. También puedes utilizar la opción --load-path del comando sass.

La regla @import espera como argumento el nombre del archivo a importar. Por defecto busca un archivo Sass y lo importar directamente, pero a veces esta regla se deja tal cual al compilar el archivo CSS:

  • Si la extensión del archivo importado es .css
  • Si el nombre del archivo empieza por http://
  • Si el nombre del archivo se indica mediante url()
  • Si la regla @import tiene alguna media query

Si no se da ninguna de las anteriores circunstancias, y la extensión del archivo importado es .scss o .sass, entonces se importan directamente los contenidos de ese archivo. Si no se indica la extensión, Sass tratará de buscar un archivo con ese nombre y con las extensiones .scss o .sass. Ejemplos:

Regla @ Resultado
@import "foo.scss"; Se importa el archivo foo.scss
@import "foo"; Se importa el archivo foo.scss
@import "foo.css"; @import "foo.css";
@import "foo" screen; @import "foo" screen;
@import "http://foo.com/bar"; @import "http://foo.com/bar";
@import url(foo); @import url(foo);

También es posible importar varios archivos con una sola regla @import. Ejemplo:

@import "rounded-corners", "text-shadow";

Esta regla importaría tanto el archivo rounded-corners como el archivo text-shadow.

El nombre del archivo importado también se puede establecer con la interpolación #{ }, pero con ciertas restricciones. No se puede importar dinámicamente un archivo Sass en base al nombre de una variable, pero sí que se puede importar de esta manera un archivo CSS. De forma que la interpolación solamente funciona en la práctica cuando se utiliza url(). Ejemplo:

$family: unquote("Droid+Sans");
@import url("http://fonts.googleapis.com/css?family=#{$family}");

El código Sass anterior se compila de la siguiente manera:

@import url("http://fonts.googleapis.com/css?family=Droid+Sans");

7.1.1. Hojas de estilos parciales

Si quieres importar un archivo SCSS o Sass pero no quieres que se compile como archivo CSS, utiliza un guión bajo como primer carácter del nombre del archivo. De esta manera, Sass no generará un archivo CSS para esa hoja de estilos, pero podrás utilizarla importándola dentro de otra hoja de estilos. Este tipo de archivos que no se compilan se denominan "hojas de estilos parciales" o simplemente "parciales" (en inglés, "partials").

Aunque el nombre del archivo tenga un guión bajo, no es necesario indicarlo en la regla @import. Así por ejemplo, si creas un archivo llamado _colors.scss, entonces no se generará un archivo _colors.css. Sin embargo, podrás utilizarlo en tus hojas de estilos con la regla @import "colors";, que importará el archivo _colors.scss.

Obviamente no puedes tener en un mismo directorio una hoja de estilos normal y una parcial con el mismo nombre. Siguiendo el ejemplo anterior, en el mismo directorio no puedes tener un archivo llamado _colors.scss y otro llamado colors.scss.

7.1.2. Anidando relgas @import

Normalmente las reglas @import se colocan en el primer nivel jerárquico de la hoja de estilos. No obstante, también es posible colocarlas dentro de reglas CSS y reglas @media.

El funcionamiento de las reglas anidadas es el mismo, pero todos los contenidos importados se incluyen en el mismo nivel en el que se hayan importado. Si por ejemplo el archivo example.scss contiene lo siguiente:

.example {
  color: red;
}

Si importas este archivo dentro de una regla CSS:

#main {
  @import "example";
}

El archivo CSS compilado resultante sería el siguiente:

#main .example {
  color: red;
}

Los archivos importados con reglas @import anidadas no pueden contener elementos y directivas que sólo pueden colocarse en el primer nivel jerárquico de las hojas de estilos, como @mixin o @charset.

Tampoco es posible anidar reglas @import dentro de los mixin y las directivas de control.

7.2. La regla @media

Las reglas @media en Sass funcionan prácticamente igual que en CSS, con una salvedad: se pueden anidar dentro de las reglas CSS. Si incluyes una regla @media dentro de una regla CSS, se aplicará a todos los selectores que se encuentren desde esa regla hasta el primer nivel de la hoja de estilos. Esto hace que sea muy fácil definir estilos dependientes de los dispositivos sin tener que repetir los selectores y sin tener que romper el flujo normal de la hoja de estilos Sass. Ejemplo:

.sidebar {
  width: 300px;
  @media screen and (orientation: landscape) {
    width: 500px;
  }
}

El código Sass anterior se compila de la siguiente manera:

.sidebar {
  width: 300px;
}
 
@media screen and (orientation: landscape) {
  .sidebar {
    width: 500px;
  }
}

Las reglas @media también se pueden anidar entre sí. El resultado la combinación de todas ellas utilizando el operador and. Ejemplo:

@media screen {
  .sidebar {
    @media (orientation: landscape) {
      width: 500px;
    }
  }
}

El código Sass anterior se compila de la siguiente manera:

@media screen and (orientation: landscape) {
  .sidebar {
    width: 500px;
  }
}

Por último, las reglas @media también pueden contener expresiones SassScript (incluyendo variables, funciones y operadores) tanto en los nombres como en los valores. Ejemplo:

$media: screen;
$feature: -webkit-min-device-pixel-ratio;
$value: 1.5;
 
@media #{$media} and ($feature: $value) {
  .sidebar {
    width: 500px;
  }
}

El código Sass anterior se compila de la siguiente manera:

@media screen and (-webkit-min-device-pixel-ratio: 1.5) {
  .sidebar {
    width: 500px;
  }
}

7.3. La regla @extend

En ocasiones, es necesario que una clase CSS contenga todos los estilos aplicados a otra regla CSS, además de sus propios estilos. La solución habitual en estos casos consiste en crear una clase genérica que puedan utilizar los dos elementos. Imagina que quieres aplicar estilos a dos tipos de mensajes de error diferentes, uno normal y otro más grave. El código HTML podría ser algo como:

<div class="error seriousError">
  ¡Acabas de ser hackeado!
</div>

Los estilos CSS podrían ser los siguientes:

.error {
  border: 1px #f00;
  background-color: #fdd;
}
.seriousError {
  border-width: 3px;
}

El problema de esta solución es que tienes que acordarte que siempre que apliques la clase .seriousError también tienes que aplicar la clase .error. Esto hace que el mantenimiento de las hojas de estilos se complique y el código HTML de las páginas se complique sin una justificación clara.

Gracias a la regla @extend puedes evitar todos estilos problemas. Esta regla le indica a Sass que un determinado selector debería heredar todos los estilos de otro selector. Ejemplo:

.error {
  border: 1px #f00;
  background-color: #fdd;
}
.seriousError {
  @extend .error;
  border-width: 3px;
}

El código Sass anterior se compila de la siguiente manera:

.error, .seriousError {
  border: 1px #f00;
  background-color: #fdd;
}
 
.seriousError {
  border-width: 3px;
}

Ahora, todos los estilos que definas para el selector .error también se aplican automáticamente al selector .seriousError, al margen de los estilos propios que pueda definir .seriousError. En la práctica esto significa que cuando apliques la clase .seriousError es como si estuvieras aplicando a la vez la clase .error.

Cualquier otra regla que se aplique al selector .error también se aplicará al selector .seriousError. Imagina que defines el siguiente estilo que se aplica simultáneamente a dos clases CSS:

.error.intrusion {
  background-image: url("/image/hacked.png");
}

Si ahora añades en tus páginas un elemento como <div class="seriousError intrusion">, también se le aplicará el estilo definido por el selector .error.intrusion.

7.3.1. Funcionamiento interno

Cuando se utiliza la regla @extend, Sass inserta ese selector (por ejemplo .seriousError) en cualquier sitio de la hoja de estilos donde aparezca el otro selector (por ejemplo .error). Ejemplo:

.error {
  border: 1px #f00;
  background-color: #fdd;
}
.error.intrusion {
  background-image: url("/image/hacked.png");
}
.seriousError {
  @extend .error;
  border-width: 3px;
}

El código Sass anterior se compila de la siguiente manera:

.error, .seriousError {
  border: 1px #f00;
  background-color: #fdd;
}
 
.error.intrusion, .seriousError.intrusion {
  background-image: url("/image/hacked.png");
}
 
.seriousError {
  border-width: 3px;
}

Al combinar los selectores, la regla @extend es lo bastante inteligente como para evitar las duplicidades innecesarias (un selector como .seriousError.seriousError se transforma automáticamente en .seriousError). También tiene en cuenta los selectores que nunca podrían seleccionar ningún elemento, como por ejemplo #main#footer.

7.3.2. Extendiendo selectores complejos

Además de los selectores de clase, Sass permite extender cualquier otro elemento que haga referencia a un único elemento, como por ejemplo .special.cool, a:hover o a.user[href^="http://"]. Ejemplo:

.hoverlink {
  @extend a:hover;
}

Al igual que en el caso de los selectores de clase, este estilo implica que todos los estilos definidos para el selector a:hover también se aplicarán al selector .hoverlink. Ejemplo:

.hoverlink {
  @extend a:hover;
}
a:hover {
  text-decoration: underline;
}

El código Sass anterior se compila de la siguiente manera:

a:hover, .hoverlink {
  text-decoration: underline;
}

Al igual que sucedía antes con el selector .error.intrusion, cualquier regla que utilice el selector a:hover también funcionará para el selector .hoverlink, incluso cuando se combinan con otros selectores. Ejemplo:

.hoverlink {
  @extend a:hover;
}
.comment a.user:hover {
  font-weight: bold;
}

El código Sass anterior se compila de la siguiente manera:

.comment a.user:hover, .comment .user.hoverlink {
  font-weight: bold;
}

7.3.3. Extendiendo de varios selectores

Los selectores pueden extender de más de un selector para heredar todos sus estilos. Ejemplo

.error {
  border: 1px #f00;
  background-color: #fdd;
}
.attention {
  font-size: 3em;
  background-color: #ff0;
}
.seriousError {
  @extend .error;
  @extend .attention;
  border-width: 3px;
}

El código Sass anterior se compila de la siguiente manera:

.error, .seriousError {
  border: 1px #f00;
  background-color: #fdd;
}
 
.attention, .seriousError {
  font-size: 3em;
  background-color: #ff0;
}
 
.seriousError {
  border-width: 3px;
}

En este ejemplo, cualquier elemento con la clase .seriousError es como si también tuviera aplicadas las clases .error y .attention. Como importa el orden en el que se extienden los selectores, el selector .seriousError tiene un color de fondo igual a #ff0 en vez de #fdd, ya que .attention se define después que .error.

La extensión de más de un selector también se puede indicar mediante una lista de selectores separados por comas. Así por ejemplo, el código @extend .error, .attention es equivalente a @extend .error; @extend.attention.

7.3.4. Extendiendo a varios niveles

Sass también permite extender de un selector que a su vez extiende de otro selector diferente. Ejemplo:

.error {
  border: 1px #f00;
  background-color: #fdd;
}
.seriousError {
  @extend .error;
  border-width: 3px;
}
.criticalError {
  @extend .seriousError;
  position: fixed;
  top: 10%;
  bottom: 10%;
  left: 10%;
  right: 10%;
}

Ahora aplicar la clase .seriousError equivale también a aplicar la clase .error y la clase .criticalError equivale a aplicar también las clases .seriousError y .error. El código Sass anterior se compila de la siguiente manera:

.error, .seriousError, .criticalError {
  border: 1px #f00;
  background-color: #fdd;
}
 
.seriousError, .criticalError {
  border-width: 3px;
}
 
.criticalError {
  position: fixed;
  top: 10%;
  bottom: 10%;
  left: 10%;
  right: 10%;
}

7.3.5. Secuencias de selectores

Las secuencias de selectores, como por ejemplo .foo .bar o .foo + .bar, todavía no se pueden extender. No obstante, sí que es posible utilizar la regla @extend en los selectores anidados. Ejemplo:

#fake-links .link {
  @extend a;
}
 
a {
  color: blue;
  &:hover {
    text-decoration: underline;
  }
}

El código Sass anterior se compila de la siguiente manera:

a, #fake-links .link {
  color: blue;
}
a:hover, #fake-links .link:hover {
  text-decoration: underline;
}

7.3.5.1. Combinando secuencias de selectores

En ocasiones una secuencia de selectores extiende otro selector que está incluido en otra secuencia de selectores. En este caso, se combinan las dos secuencias de selectores. Ejemplo:

#admin .tabbar a {
  font-weight: bold;
}
#demo .overview .fakelink {
  @extend a;
}

Aunque técnicamente sería posible generar todos los selectores resultantes de combinar todos los selectores entre sí, esto haría que la hoja de estilos resultante fuera demasiado larga. Un código tan sencillo como el mostrado anteriormente generaría por ejemplo diez selectores. Así que en vez de generar todas las combinaciones posibles, Sass solamente genera aquellos selectores que probablemente van a ser de utilidad.

Cuando las dos secuencias que se van a combinar no tienen selectores en común, entonces se generan dos nuevos selectores: uno con la primera secuencia por delante de la segunda y otro con la segunda secuencia por delante de la primera. Ejemplo:

#admin .tabbar a {
  font-weight: bold;
}
#demo .overview .fakelink {
  @extend a;
}

El código Sass anterior se compila de la siguiente manera:

#admin .tabbar a,
#admin .tabbar #demo .overview .fakelink,
#demo .overview #admin .tabbar .fakelink {
  font-weight: bold;
}

Si las dos secuencias tienen algunos selectores en común, se combinan esos selectores y las diferencias, si exsten, se alternan. En el siguiente ejemplo, las dos secuencias tienen el selector #admin, así que los selectores resultantes serán el resultado de combinar esos dos selectores de id:

#admin .tabbar a {
  font-weight: bold;
}
#admin .overview .fakelink {
  @extend a;
}

El código Sass anterior se compila de la siguiente manera:

#admin .tabbar a,
#admin .tabbar .overview .fakelink,
#admin .overview .tabbar .fakelink {
  font-weight: bold;
}

7.3.6. Selectores exclusivos para reglas @extend

Las hojas de estilos también pueden contener clases que no se utilizan directamente en el código HTML y que sólo se definen para agrupar estilos que luego se utilizan mediante reglas @extend. Esto es común cuando se escriben librerías para Sass, ya que puede ser interesante ofrecer a los diseñadores la posibilidad de extender o ignorar algunas clases en sus estilos.

Si utilizaras clases normales, acabarías generando un código CSS demasiado grande y poco optimizado. Incluso correrías el peligro de generar colisiones con otras clases que sí que se utilizan en el código HTML. Por este motivo Sass soporta los selectores variables con la sintaxis %foo.

Los selectores variables (en inglés, "placeholder parameters") se parecen a los selectores de clase o de id, pero utilizan el carácter % en vez de . o #. Estos nuevos selectores se pueden utilizar en cualquier lugar en el que utilices los selectores de clase o de id y están preparados para no generar código CSS al compilar las hojas de estilos. Ejemplo:

// Este estilo no se incluirá en el archivo CSS compilado
#context a%extreme {
  color: blue;
  font-weight: bold;
  font-size: 2em;
}

La ventaja de los selectores variables es que se pueden extender, de la misma manera que el resto de selectores. Ejemplo:

.notice {
  @extend %extreme;
}

El código Sass anterior se compila de la siguiente manera:

#context a.notice {
  color: blue;
  font-weight: bold;
  font-size: 2em;
}

7.3.7. La opción !optional

Cuando extiendes un selector que no existe, Sass genera un error. Si utilizas por ejemplo el código a.important {@extend .notice} pero no existe el selector .notice, entonces se produce un error. También se produciría un error si el único selector que contiene la clase .notice fuera h1.notice, ya que h1 entraría en conflicto con a y no se generaría ningún selector.

No obstante, en ocasiones puede ser útil permitir que @extend no genere ningún selector. Para ello, añade la opción !optional justo después del selector. Ejemplo:

a.important {
  @extend .notice !optional;
}

7.3.8. Usando @extend en las directivas

Existen algunas restricciones que impiden usar @extend en el interior de directivas como @media. Sass por ejemplo no es capaz de hacer que las reglas CSS que se encuentran fuera de la directiva @media se apliquen a los selectores de su interior sin generar un código CSS gigantesco con selectores y estilos duplicados por todas partes. Por lo tanto, si utilizas @extend con la directiva @media o con otras directivas CSS, sólo debes extender los selectores que están encerrados por esas directivas.

El siguiente ejemplo funciona correctamente:

@media print {
  .error {
    border: 1px #f00;
    background-color: #fdd;
  }
  .seriousError {
    @extend .error;
    border-width: 3px;
  }
}

Pero el siguiente código produciría un error:

.error {
  border: 1px #f00;
  background-color: #fdd;
}
 
@media print {
  .seriousError {
    // ESTILO INVÁLIDO: .error se utiliza fuera de la directiva "@media print"
    @extend .error;
    border-width: 3px;
  }
}

En un futuro es posible que los navegadores soporten de manera nativa la directiva @extend, por lo que sería posible utilizarla dentro de @media y otras directivas.

7.4. La regla @at-root

Las directivas @at-root hacen que una o más reglas se generen en la raíz de la hoja de estilos en vez de anidarse en sus selectores. Se puede utilizar tanto con selectores individuales como con bloques de selectores. Ejemplo:

// selector individual
.parent {
  @at-root .child { ... }
}
 
// bloques de selectores
.parent {
  @at-root {
    .child1 { ... }
    .child2 { ... }
  }
}

El código Sass anterior se compila de la siguiente manera:

.child { ... }
 
.child1 { ... }
.child2 { ... }

7.4.1. Modificando la regla @at-root con with y without

Por defecto la regla @at-root simplemente excluye todos los selectores. No obstante, también es posible modificar su comportamiento para que salga o no de cualquier directiva @media en la que se encuentre esa regla. Ejemplo:

@media print {
  .page {
    width: 8in;
    @at-root (without: media) {
      color: red;
    }
  }
}

El código Sass anterior se compila de la siguiente manera:

@media print {
  .page {
    width: 8in;
  }
}
.page {
  color: red;
}

La regla @at-root (without: ...) hace que el estilo se aplique en la raíz de la hoja de estilos y fuera de cualquier media query. También es posible excluir varias directivas separándolas con espacios en blanco: @at-root (without: media supports) saca los estilos fuera de las queries @media y @supports.

La regla @at-root admite otros dos valores especiales. El valor rule se refiere a las reglas CSS normales, por lo que @at-root (without: rule) es equivalente a @at-root sin ninguna query. Por su parte, la regla @at-root (without: all) significa que los estilos deben sacarse de cualquier directiva o regla CSS.

Si en vez de indicar las directivas o reglas CSS que se excluyen quieres indicar explícitamente las que se inlcuyen, utiliza with en vez de without. Así por ejemplo, los estilos @at-root (with: rule) se moverán fuera de cualquier directiva pero mantendrán todas las reglas CSS.

7.5. La regla @debug

La regla @debug muestra por la consola el valor de la expresión SassScript indicada. Se trata de una regla útil para depurar hojas de estilos muy complejas y que utilizan expresiones SassScript muy avanzadas. Ejemplo:

@debug 10em + 12em;

El código anterior mostraría en la consola el siguiente mensaje:

Line 1 DEBUG: 22em

7.6. La regla @warn

La regla @warn muestra el valor de una expresió SaasScript en forma de mensaje de error. Se trata de una regla muy útil para que los creadores de las librerías avisen a los diseñadores sobre el uso de características que se han declarado obsoletas. También sirve para mostrar errores en el uso de mixins que Sass ha podido corregir automáticamente. Existen dos diferencias principales entre @warn y @debug:

  1. Puedes desactivar los mensajes de error con la opción --quiet de la línea de comandos o con la opción de configuración :quiet de Sass.
  2. Los mensajes de error de @warn también se incluyen en la hoja de estilos generada para que el usuario pueda ver tanto los errores como el lugar exacto en el que se producen.

Ejemplo:

@mixin adjust-location($x, $y) {
  @if unitless($x) {
    @warn "Assuming #{$x} to be in pixels";
    $x: 1px * $x;
  }
  @if unitless($y) {
    @warn "Assuming #{$y} to be in pixels";
    $y: 1px * $y;
  }
  position: relative; left: $x; top: $y;
}