Aplicar estilo a las aplicaciones mediante hojas de estilos en cascada

Las aplicaciones .NET Multi-platform App UI (.NET MAUI) pueden aplicar estilos mediante hojas de estilos en cascada (CSS). Una hoja de estilos consta de una lista de reglas, con cada regla formada por uno o varios selectores y un bloque de declaración. Un bloque de declaración consta de una lista de declaraciones entre llaves, con cada declaración formada por una propiedad, dos puntos y un valor. Cuando hay varias declaraciones en un bloque, se inserta un punto y coma como separador.

En el ejemplo siguiente se muestran algunas CSS compatibles con .NET MAUI:

navigationpage {
    -maui-bar-background-color: lightgray;
}

^contentpage {
    background-color: lightgray;
}

#listView {
    background-color: lightgray;
}

stacklayout {
    margin: 20;
    -maui-spacing: 6;
}

grid {
    row-gap: 6;
    column-gap: 6;
}
.mainPageTitle {
    font-style: bold;
    font-size: 14;
}

.mainPageSubtitle {
    margin-top: 15;
}

.detailPageTitle {
    font-style: bold;
    font-size: 14;
    text-align: center;
}

.detailPageSubtitle {
    text-align: center;
    font-style: italic;
}

listview image {
    height: 60;
    width: 60;
}

stacklayout>image {
    height: 200;
    width: 200;
}

En .NET MAUI, las hojas de estilos CSS se analizan y evalúan en tiempo de ejecución, en lugar de en tiempo de compilación, y las hojas de estilos se vuelven a analizar cuando se usan.

Importante

No es posible aplicar un estilo completo a una aplicación .NET MAUI mediante CSS. Pero los estilos XAML se pueden usar para complementar CSS. Para más información sobre los estilos XAML, consulta Aplicación de estilo a aplicaciones con XAML.

Consumo de una hoja de estilos

El proceso para agregar una hoja de estilos a una aplicación .NET MAUI es el siguiente:

  1. Agrega un archivo CSS vacío al proyecto de aplicación de NET MAUI. El archivo CSS se puede colocar en cualquier carpeta y la carpeta Resources es la ubicación recomendada.
  2. Establece la acción de compilación del archivo CSS en MauiCss.

Carga de una hoja de estilos

Hay una serie de enfoques que se pueden usar para cargar una hoja de estilos.

Nota:

No es posible cambiar una hoja de estilos en tiempo de ejecución y aplicar la nueva hoja de estilos.

Cargar una hoja de estilos en XAML

Una hoja de estilos se puede cargar y analizar con la clase StyleSheet antes de agregarse a ResourceDictionary:

<Application ...>
    <Application.Resources>
        <StyleSheet Source="/Resources/styles.css" />
    </Application.Resources>
</Application>

La propiedad StyleSheet.Source especifica la hoja de estilos como un URI relativo a la ubicación del archivo XAML envolvente, o en relación con la raíz del proyecto si el URI empieza por /.

Advertencia

El archivo CSS no se cargará si su acción de compilación no está establecida en MauiCss.

Como alternativa, una hoja de estilos se puede cargar y analizar con la clase StyleSheet, antes de agregarla a un elemento ResourceDictionary, insertándola en una sección CDATA:

<ContentPage ...>
    <ContentPage.Resources>
        <StyleSheet>
            <![CDATA[
            ^contentpage {
                background-color: lightgray;
            }
            ]]>
        </StyleSheet>
    </ContentPage.Resources>
    ...
</ContentPage>

Para más información sobre los recursos, consulta Diccionarios de recursos.

Cargar una hoja de estilos en C#

En C#, se puede cargar una hoja de estilos desde StringReader y agregarse a ResourceDictionary:

using Microsoft.Maui.Controls.StyleSheets;

public partial class MyPage : ContentPage
{
    public MyPage()
    {
        InitializeComponent();

        using (var reader = new StringReader("^contentpage { background-color: lightgray; }"))
        {
            this.Resources.Add(StyleSheet.FromReader(reader));
        }
    }
}

El argumento para el método StyleSheet.FromReader es TextReader que ha leído la hoja de estilos.

Seleccionar elementos y aplicar propiedades

CSS usa selectores para determinar a qué elementos va dirigido. Los estilos con selectores coincidentes se aplican consecutivamente, en orden de definición. Los estilos definidos en un elemento específico siempre se aplican en último lugar. Para más información sobre los selectores admitidos, consulta Referencia del selector.

CSS usa propiedades para aplicar estilo a un elemento seleccionado. Cada propiedad tiene un conjunto de valores posibles y algunas propiedades pueden afectar a cualquier tipo de elemento, mientras que otras se aplican a grupos de elementos. Para más información sobre las propiedades admitidas, consulta Referencia de propiedad.

Las hojas de estilos secundarias siempre invalidan las hojas de estilos primarias si establecen las mismas propiedades. Por lo tanto, se siguen las siguientes reglas de precedencia al aplicar estilos que establecen las mismas propiedades:

  • Un estilo definido en los recursos de la aplicación se sobrescribirá mediante un estilo definido en los recursos de página, si establecen las mismas propiedades.
  • Un estilo definido en los recursos de página se sobrescribirá mediante un estilo definido en los recursos de control, si establecen las mismas propiedades.
  • Un estilo definido en los recursos de la aplicación se sobrescribirá mediante un estilo definido en los recursos de control, si establecen las mismas propiedades.

Nota:

No se admiten variables CSS.

Seleccionar elementos por tipo

Los elementos del árbol visual se pueden seleccionar por tipo con el selector element sin distinción entre mayúsculas y minúsculas:

stacklayout {
    margin: 20;
}

Este selector identifica los elementos StackLayout de las páginas que consumen la hoja de estilos y establece sus márgenes en un grosor uniforme de 20.

Nota:

El selector element no identifica las subclases del tipo especificado.

Selección de elementos por clase base

Los elementos del árbol visual se pueden seleccionar mediante la clase base con el selector ^base sin distinción entre mayúsculas y minúsculas:

^contentpage {
    background-color: lightgray;
}

Este selector identifica los elementos ContentPage que consumen la hoja de estilos y establece su color de fondo en lightgray.

Nota:

El selector ^base es específico de .NET MAUI y no forma parte de la especificación de CSS.

Selección de un elemento por nombre

Los elementos individuales del árbol visual se pueden seleccionar con el selector #id sin distinción entre mayúsculas y minúsculas:

#listView {
    background-color: lightgray;
}

Este selector identifica el elemento cuya propiedad StyleId está establecida en listView. Pero si la propiedad StyleId no está establecida, el selector volverá a usar el atributo x:Name del elemento. Por lo tanto, en el ejemplo siguiente, el selector #listView identificará el atributo ListView cuyo atributo x:Name está establecido en listView y establecerá el color de fondo en lightgray.

<ContentPage ...>
    <ContentPage.Resources>
        <StyleSheet Source="/Resources/styles.css" />
    </ContentPage.Resources>
    <StackLayout>
        <ListView x:Name="listView">
            ...
        </ListView>
    </StackLayout>
</ContentPage>

Selección de elementos con un atributo de clase específico

Los elementos con un atributo de clase específico se pueden seleccionar con el selector .class sin distinción entre mayúsculas y mayúsculas:

.detailPageTitle {
    font-style: bold;
    font-size: 14;
    text-align: center;
}

.detailPageSubtitle {
    text-align: center;
    font-style: italic;
}

Una clase CSS se puede asignar a un elemento XAML estableciendo la propiedad StyleClass del elemento en el nombre de clase CSS. Por lo tanto, en el ejemplo siguiente, los estilos definidos por la clase .detailPageTitle se asignan al primer elemento Label, mientras que los estilos definidos por la clase .detailPageSubtitle se asignan al segundo elemento Label.

<ContentPage ...>
    <ContentPage.Resources>
        <StyleSheet Source="/Resources/styles.css" />
    </ContentPage.Resources>
    <ScrollView>
        <StackLayout>
            <Label ... StyleClass="detailPageTitle" />
            <Label ... StyleClass="detailPageSubtitle"/>
        </StackLayout>
    </ScrollView>
</ContentPage>

Selección de elementos secundarios

Los elementos secundarios del árbol visual se pueden seleccionar con el selector element elementsin distinción entre mayúsculas y minúsculas:

listview image {
    height: 60;
    width: 60;
}

Este selector identifica los elementos secundarios Image de los elementos ListView y establece su alto y ancho en 60. Por lo tanto, en el ejemplo de XAML siguiente, el selector listview image identificará que Image es un elemento secundario de ListView y establece su alto y ancho en 60.

<ContentPage ...>
    <ContentPage.Resources>
        <StyleSheet Source="/Resources/styles.css" />
    </ContentPage.Resources>
    <StackLayout>
        <ListView ...>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid>
                            ...
                            <Image ... />
                            ...
                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>
</ContentPage>

Nota:

El selector element element no requiere que el elemento secundario sea un elemento secundario directo del elemento primario: el elemento secundario puede tener un elemento primario diferente. La selección se produce siempre que un antecesor sea el primer elemento especificado.

Selección de elementos secundarios directos

Los elementos secundarios directos del árbol visual se pueden seleccionar con el selector element>element sin distinción entre mayúsculas y minúsculas:

stacklayout>image {
    height: 200;
    width: 200;
}

Este selector identifica todos los elementos Image que son elementos secundarios directos de StackLayout elementos y establece su alto y ancho en 200. Por lo tanto, en el ejemplo siguiente, el selector stacklayout>image identificará que Image es un elemento secundario directo de StackLayout y establece su alto y ancho en 200.

<ContentPage ...>
    <ContentPage.Resources>
        <StyleSheet Source="/Resources/styles.css" />
    </ContentPage.Resources>
    <ScrollView>
        <StackLayout>
            ...
            <Image ... />
            ...
        </StackLayout>
    </ScrollView>
</ContentPage>

Nota:

El selector element>element requiere que el elemento secundario sea un elemento secundario directo del elemento primario.

Referencia del selector

Los siguientes selectores CSS son compatibles con .NET MAUI:

Selector Ejemplo Descripción
.class .header Selecciona todos los elementos con la propiedad StyleClass que contiene "header". Este selector distingue mayúsculas de minúsculas.
#id #email Selecciona todos los elementos con StyleId establecido en email. Si StyleId no se establece, se usará x:Name. Al usar XAML, x:Name es preferible a StyleId. Este selector distingue mayúsculas de minúsculas.
* * Selecciona todos los elementos.
element label Selecciona todos los elementos de tipo Label, pero no subclases. Este selector no distingue mayúsculas de minúsculas.
^base ^contentpage Selecciona todos los elementos con ContentPage como clase base, incluido el propio elemento ContentPage. Este selector no distingue mayúsculas de minúsculas y no forma parte de la especificación CSS.
element,element label,button Selecciona todos los elementos Button y todos los elementos Label. Este selector no distingue mayúsculas de minúsculas.
element element stacklayout label Selecciona todos los elementos Label dentro de StackLayout. Este selector no distingue mayúsculas de minúsculas.
element>element stacklayout>label Selecciona todos los elementos Label con StackLayout como elemento primario directo. Este selector no distingue mayúsculas de minúsculas.
element+element label+entry Selecciona todos los elementos Entry directamente después de Label. Este selector no distingue mayúsculas de minúsculas.
element~element label~entry Selecciona todos los elementos Entry precedidos de Label. Este selector no distingue mayúsculas de minúsculas.

Los estilos con selectores coincidentes se aplican consecutivamente, en orden de definición. Los estilos definidos en un elemento específico siempre se aplican en último lugar.

Sugerencia

Los selectores se pueden combinar sin limitación, como StackLayout>ContentView>label.email.

No se admiten los siguientes selectores:

  • [attribute]
  • @media y @supports
  • : y ::

Nota:

No se admiten la especificidad ni la anulación de especificidad.

Referencia de propiedades

.NET MAUI admite las siguientes propiedades CSS (en la columna Valores, los tipos están en cursiva, mientras que los literales de cadena son gray):

Propiedad Se aplica a Valores Ejemplo
align-content FlexLayout stretch | center | start | end | spacebetween | spacearound | spaceevenly | flex-start | flex-end | space-between | space-around | initial align-content: space-between;
align-items FlexLayout stretch | center | start | end | flex-start | flex-end | initial align-items: flex-start;
align-self VisualElement auto | stretch | center | start | end | flex-start | flex-end | initial align-self: flex-end;
background-color VisualElement color | initial background-color: springgreen;
background-image Page string | initial background-image: bg.png;
border-color Button, Frame, ImageButton color | initial border-color: #9acd32;
border-radius BoxView, Button, Frame, ImageButton double | initial border-radius: 10;
border-width Button, ImageButton double | initial border-width: .5;
color ActivityIndicator, BoxView, Button, CheckBox, DatePicker, Editor, Entry, Label, Picker, ProgressBar, SearchBar, Switch, TimePicker color | initial color: rgba(255, 0, 0, 0.3);
column-gap Grid double | initial column-gap: 9;
direction VisualElement ltr | rtl | inherit | initial direction: rtl;
flex-direction FlexLayout column | columnreverse | row | rowreverse | row-reverse | column-reverse | initial flex-direction: column-reverse;
flex-basis VisualElement float | auto | initial Además, se puede especificar un rango del 0 % al 100 % con el signo %. flex-basis: 25%;
flex-grow VisualElement float | initial flex-grow: 1.5;
flex-shrink VisualElement float | initial flex-shrink: 1;
flex-wrap VisualElement nowrap | wrap | reverse | wrap-reverse | initial flex-wrap: wrap-reverse;
font-family Button, DatePicker, Editor, Entry, Label, Picker, SearchBar, TimePicker, Span string | initial font-family: Consolas;
font-size Button, DatePicker, Editor, Entry, Label, Picker, SearchBar, TimePicker, Span double | initial font-size: 12;
font-style Button, DatePicker, Editor, Entry, Label, Picker, SearchBar, TimePicker, Span bold | italic | initial font-style: bold;
height VisualElement double | initial height: 250;
justify-content FlexLayout start | center | end | spacebetween | spacearound | spaceevenly | flex-start | flex-end | space-between | space-around | initial justify-content: flex-end;
letter-spacing Button, DatePicker, Editor, Entry, Label, Picker, SearchBar, SearchHandler, Span, TimePicker double | initial letter-spacing: 2.5;
line-height Label, Span double | initial line-height: 1.8;
margin View grosor | initial margin: 6 12;
margin-left View grosor | initial margin-left: 3;
margin-top View grosor | initial margin-top: 2;
margin-right View grosor | initial margin-right: 1;
margin-bottom View grosor | initial margin-bottom: 6;
max-lines Label int | initial max-lines: 2;
min-height VisualElement double | initial min-height: 50;
min-width VisualElement double | initial min-width: 112;
opacity VisualElement double | initial opacity: .3;
order VisualElement int | initial order: -1;
padding Button, ImageButton, Layout, Page grosor | initial padding: 6 12 12;
padding-left Button, ImageButton, Layout, Page double | initial padding-left: 3;
padding-top Button, ImageButton, Layout, Page double | initial padding-top: 4;
padding-right Button, ImageButton, Layout, Page double | initial padding-right: 2;
padding-bottom Button, ImageButton, Layout, Page double | initial padding-bottom: 6;
position FlexLayout relative | absolute | initial position: absolute;
row-gap Grid double | initial row-gap: 12;
text-align Entry, EntryCell, Label, SearchBar left | top | right | bottom | start | center | middle | end | initial. left y right deben evitarse en entornos de derecha a izquierda. text-align: right;
text-decoration Label, Span none | underline | strikethrough | line-through | initial text-decoration: underline, line-through;
text-transform Button, Editor, Entry, Label, SearchBar, SearchHandler none | default | uppercase | lowercase | initial text-transform: uppercase;
transform VisualElement none, rotate, rotateX, rotateY, scale, scaleX, scaleY, translate, translateX, translateY, initial transform: rotate(180), scaleX(2.5);
transform-origin VisualElement double, double | initial transform-origin: 7.5, 12.5;
vertical-align Label left | top | right | bottom | start | center | middle | end | initial vertical-align: bottom;
visibility VisualElement true | visible | false | hidden | collapse | initial visibility: hidden;
width VisualElement double | initial width: 320;

Nota:

initial es un valor válido para todas las propiedades. Borra el valor (se restablece al valor predeterminado) que se estableció desde otro estilo.

No se admiten las siguientes propiedades:

  • all: initial.
  • Propiedades de diseño (cuadro o cuadrícula).
  • Propiedades abreviadas, como font y border.

Además, no hay ningún valor inherit y, por tanto, no se admite la herencia. Por lo tanto, no puedes, por ejemplo, establecer la propiedad font-size en un diseño y esperar que todas las instancias Label del diseño hereden el valor. La única excepción es la propiedad direction, que tiene un valor predeterminado de inherit.

Importante

Los elementos Span no se pueden destinar mediante CSS.

Propiedades específicas de .NET MAUI

También se admiten las siguientes propiedades CSS específicas de .NET MAUI (en la columna Valores, los tipos están en cursiva, mientras que los literales de cadena son gray):

Propiedad Se aplica a Valores Ejemplo
-maui-bar-background-color NavigationPage, TabbedPage color | initial -maui-bar-background-color: teal;
-maui-bar-text-color NavigationPage, TabbedPage color | initial -maui-bar-text-color: gray
-maui-horizontal-scroll-bar-visibility ScrollView default | always | never | initial -maui-horizontal-scroll-bar-visibility: never;
-maui-max-length Entry, Editor, SearchBar int | initial -maui-max-length: 20;
-maui-max-track-color Slider color | initial -maui-max-track-color: red;
-maui-min-track-color Slider color | initial -maui-min-track-color: yellow;
-maui-orientation ScrollView, StackLayout horizontal | vertical | both | initial. both solo se admite en ScrollView. -maui-orientation: horizontal;
-maui-placeholder Entry, Editor, SearchBar quoted text | initial -maui-placeholder: Enter name;
-maui-placeholder-color Entry, Editor, SearchBar color | initial -maui-placeholder-color: green;
-maui-spacing StackLayout double | initial -maui-spacing: 8;
-maui-thumb-color Slider, Switch color | initial -maui-thumb-color: limegreen;
-maui-vertical-scroll-bar-visibility ScrollView default | always | never | initial -maui-vertical-scroll-bar-visibility: always;
-maui-vertical-text-alignment Label start | center | end | initial -maui-vertical-text-alignment: end;
-maui-visual VisualElement string | initial -maui-visual: material;

Propiedades específicas de .NET MAUI Shell

También se admiten las siguientes propiedades CSS específicas de .NET MAUI Shell (en la columna Valores, los tipos están en cursiva, mientras que los literales de cadena son gray):

Propiedad Se aplica a Valores Ejemplo
-maui-flyout-background Shell color | initial -maui-flyout-background: red;
-maui-shell-background Element color | initial -maui-shell-background: green;
-maui-shell-disabled Element color | initial -maui-shell-disabled: blue;
-maui-shell-foreground Element color | initial -maui-shell-foreground: yellow;
-maui-shell-tabbar-background Element color | initial -maui-shell-tabbar-background: white;
-maui-shell-tabbar-disabled Element color | initial -maui-shell-tabbar-disabled: black;
-maui-shell-tabbar-foreground Element color | initial -maui-shell-tabbar-foreground: gray;
-maui-shell-tabbar-title Element color | initial -maui-shell-tabbar-title: lightgray;
-maui-shell-tabbar-unselected Element color | initial -maui-shell-tabbar-unselected: cyan;
-maui-shell-title Element color | initial -maui-shell-title: teal;
-maui-shell-unselected Element color | initial -maui-shell-unselected: limegreen;

Color

Se admiten los valores color siguientes:

  • X11colores, que coinciden con los colores CSS y los colores .NET MAUI Todos los valores distinguen entre mayúsculas y minúsculas.
  • colores hexadecimales: #rgb, #argb, #rrggbb, #aarrggbb
  • colores rgb: rgb(255,0,0), rgb(100%,0%,0%). Los valores válidos están en el rango entre 0 y 255 o entre 0 % y 100 %.
  • colores rgba: rgba(255, 0, 0, 0.8), rgba(100%, 0%, 0%, 0.8). El valor de opacidad está en el rango 0,0-1,0.
  • Colores hsl: hsl(120, 100%, 50%). El valor h está en el rango entre 0 y 360, mientras que s y l están en el rango entre 0% y 100%.
  • colores hsla: hsla(120, 100%, 50%, .8). El valor de opacidad está en el rango 0,0-1,0.

Thickness

Se admiten uno, dos, tres o cuatro valores thickness, cada uno separado por espacios en blanco:

  • Un valor único indica un grosor uniforme.
  • Dos valores indican el grosor vertical y horizontal.
  • Tres valores indican el grosor superior, después horizontal (izquierda y derecha), después inferior.
  • Cuatro valores indican el grosor superior, después derecho, inferior e izquierdo.

Nota:

Los valores thickness CSS difieren de los valores Thickness XAML. Por ejemplo, en XAML, un valor Thickness de dos indica el grosor horizontal y vertical, mientras que un valor Thickness de cuatro indica el grosor izquierdo, después superior, derecho e inferior. Además, los valores Thickness XAML están delimitados por comas.

Funciones

Los degradados lineales y radiales se pueden especificar mediante las funciones CSS linear-gradient() y radial-gradient(), respectivamente. El resultado de estas funciones debe asignarse a la propiedad background de un control.