Works With
- Node.js
& compatible - Vite
ecosystem - Lit
ecosystem - Web Components
& web APIs
Main Features
Define URLs from your project tree, leverage code bundle splitting.
Render streams from your templates, per-request or ahead-of-time.
Compile your project routes to an easily hosted distributable.
Augment your "Multi-Page Application" with a snappier user experience.
Add JS surgically, within "Islands", or opt for full blown hydration.
Go along with the web grain, thanks to native APIs friendliness.
Sugar and streamline your Web Components, thanks to Lit's numerous helpers.
Import SVG, frontmattered Markdown, and others; right into your templates.
Skip templates transformation, while keeping a robust editing experience.
Implement route methods for handling HTTP requests, in a type-safe manner.
Cherry pick only what's needed. Extend the framework capabilities.
Starter Projects
Home
A thin, full-stack, web framework.
Annotated Examples
You can hover/tap source code tokens, like in your local code editor, to get more insights.
📄 /src/document.ts
import { htmlfunction html(strings: TemplateStringsArray, ...values: unknown[]): ServerRenderedTemplate
A lit-html template that can only be rendered on the server, and cannot be
hydrated.
These templates can be used for rendering full documents, including the
doctype, and rendering into elements that Lit normally cannot, like
<title>
, <textarea>
, <template>
, and non-executing <script>
tags
like <script type="text/json">
. They are also slightly more efficient than
normal Lit templates, because the generated HTML doesn't need to include
markers for updating.
Server-only html
templates can be composed, and combined, and they support
almost all features that normal Lit templates do, with the exception of
features that don't have a pure HTML representation, like event handlers or
property bindings.
Server-only html
templates can only be rendered on the server, they will
throw an Error if created in the browser. However if you render a normal Lit
template inside a server-only template, then it can be hydrated and updated.
Likewise, if you place a custom element inside a server-only template, it can
be hydrated and update like normal.
A server-only template can't be rendered inside a normal Lit template.
} from '@gracile/gracile/server-html';
export const documentconst document: (props: {
url: URL;
title?: string;
}) => ServerRenderedTemplate
= (propsprops: {
url: URL;
title?: string;
}
: { urlurl: URL
: URL; titletitle?: string | undefined
?: string }) => htmlfunction html(strings: TemplateStringsArray, ...values: unknown[]): ServerRenderedTemplate
A lit-html template that can only be rendered on the server, and cannot be
hydrated.
These templates can be used for rendering full documents, including the
doctype, and rendering into elements that Lit normally cannot, like
<title>
, <textarea>
, <template>
, and non-executing <script>
tags
like <script type="text/json">
. They are also slightly more efficient than
normal Lit templates, because the generated HTML doesn't need to include
markers for updating.
Server-only html
templates can be composed, and combined, and they support
almost all features that normal Lit templates do, with the exception of
features that don't have a pure HTML representation, like event handlers or
property bindings.
Server-only html
templates can only be rendered on the server, they will
throw an Error if created in the browser. However if you render a normal Lit
template inside a server-only template, then it can be hydrated and updated.
Likewise, if you place a custom element inside a server-only template, it can
be hydrated and update like normal.
A server-only template can't be rendered inside a normal Lit template.
`
<!doctype html>
<html lang="en">
<head>
Global assets
<link rel="stylesheet" href="/src/styles/global.scss" />
<script type="module" src="/src/document.client.ts"></script>
SEO
<title>${propsprops: {
url: URL;
title?: string;
}
.titletitle?: string | undefined
?? 'My Website'}</title>
<!-- ... -->
</head>
<body>
Current route's page injection
<route-template-outlet></route-template-outlet>
</body>
</html>
`;
📄 /src/routes/index.ts
import { defineRoutefunction defineRoute<GetHandlerData extends R.HandlerDataHtml = undefined, PostHandlerData extends R.HandlerDataHtml = undefined, CatchAllHandlerData extends R.HandlerDataHtml = undefined, StaticPathOptions extends R.StaticPathOptionsGeneric | undefined = undefined, RouteContext extends R.RouteContextGeneric = {
...;
}>(options: {
handler?: StaticPathOptions extends object ? never : R.Handler<CatchAllHandlerData> | {
GET?: R.Handler<GetHandlerData>;
POST?: R.Handler<PostHandlerData>;
QUERY?: R.Handler<Response>;
PUT?: R.Handler<Response>;
PATCH?: R.Handler<Response>;
DELETE?: R.Handler<Response>;
HEAD?: R.Handler<Response>;
OPTIONS?: R.Handler<Response>;
} | undefined;
staticPaths?: () => R.MaybePromise<StaticPathOptions[]> | undefined;
prerender?: boolean | undefined;
document?: R.DocumentTemplate<RouteContext> | undefined;
template?: R.BodyTemplate<RouteContext> | undefined;
}): (RouteModule: typeof R.RouteModule) => R.RouteModule
Defines a file-based route for Gracile to consume.
} from '@gracile/gracile/route';
import { htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>
Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html
tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
} from 'lit';
import { dbconst db: {
query<T>(str: string): T[];
}
Dummy DB handler…
, sqlfunction sql(str: string): string
Dummy SQL template literal…
, Achievementtype Achievement = {
name: string;
date: Date;
}
} from '../lib/db.js';
import { documentconst document: (props: {
url: URL;
title?: string;
}) => ServerRenderedTemplate
} from '../document.js';
import homeReadmeconst homeReadme: MarkdownModule
from '../content/README.md';
import type { MyElementclass MyElement
} from '../features/my-element.ts';
import '../features/my-element.js';
export default defineRoutedefineRoute<undefined, Response | {
success: boolean;
message: "Wrong input!";
}, undefined, undefined, {
url: URL;
props: {
GET: undefined;
POST: {
success: boolean;
message: "Wrong input!";
};
};
params: Parameters;
}>(options: {
...;
}): (RouteModule: typeof RouteModule) => RouteModule
Defines a file-based route for Gracile to consume.
({
handlerhandler?: Handler<undefined> | {
GET?: Handler<undefined> | undefined;
POST?: Handler<Response | {
success: boolean;
message: "Wrong input!";
}> | undefined;
... 5 more ...;
OPTIONS?: Handler<...> | undefined;
} | undefined
A function or an object containing functions named after HTTP methods.
A handler can return either a standard Response
that will terminate the
request pipeline, or any object to populate the current route template
and document contexts.
: {
POSTPOST?: Handler<Response | {
success: boolean;
message: "Wrong input!";
}> | undefined
: async (contextcontext: {
url: URL;
params: Parameters;
request: Request;
locals: Gracile.Locals;
responseInit: ResponseInit;
}
) => {
const formDataconst formData: FormData
= await contextcontext: {
url: URL;
params: Parameters;
request: Request;
locals: Gracile.Locals;
responseInit: ResponseInit;
}
.requestrequest: Request
.formDataBody.formData(): Promise<FormData>
();
const nameconst name: string | undefined
= formDataconst formData: FormData
.getFormData.get(name: string): FormDataEntryValue | null
('achievement')?.toStringfunction toString(): string
Returns a string representation of a string.
();
if (nameconst name: string | undefined
) {
await dbconst db: {
query<T>(str: string): T[];
}
Dummy DB handler…
.queryquery<unknown>(str: string): unknown[]
(sqlfunction sql(str: string): string
Dummy SQL template literal…
`INSERT INTO achievements (name); VALUES (${nameconst name: string
})`);
return Responsevar Response: {
new (body?: BodyInit | null, init?: ResponseInit): Response;
prototype: Response;
error(): Response;
json(data: any, init?: ResponseInit): Response;
redirect(url: string | URL, status?: number): Response;
}
This Fetch API interface represents the response to a request.
.redirectfunction redirect(url: string | URL, status?: number): Response
(contextcontext: {
url: URL;
params: Parameters;
request: Request;
locals: Gracile.Locals;
responseInit: ResponseInit;
}
.urlurl: URL
, 303);
}
const messageconst message: "Wrong input!"
= 'Wrong input!' as consttype const = "Wrong input!"
;
contextcontext: {
url: URL;
params: Parameters;
request: Request;
locals: Gracile.Locals;
responseInit: ResponseInit;
}
.responseInitresponseInit: ResponseInit
Let you mutate the downstream page response.
It doesn't take effect if you're returning the
response yourself before (within your request handler).
.statusResponseInit.status?: number | undefined
= 400;
return { successsuccess: boolean
: false, messagemessage: "Wrong input!"
};
},
},
documentdocument?: DocumentTemplate<{
url: URL;
props: {
GET: undefined;
POST: {
success: boolean;
message: "Wrong input!";
};
};
params: Parameters;
}> | undefined
A function that returns a server only template.
Route context is provided at runtime during the build.
: (contextcontext: {
url: URL;
props: {
GET: undefined;
POST: {
success: boolean;
message: "Wrong input!";
};
};
params: Parameters;
}
) =>
documentfunction document(props: {
url: URL;
title?: string;
}): ServerRenderedTemplate
({ urlurl: URL
: contextcontext: {
url: URL;
props: {
GET: undefined;
POST: {
success: boolean;
message: "Wrong input!";
};
};
params: Parameters;
}
.urlurl: URL
, titletitle?: string | undefined
: homeReadmeconst homeReadme: MarkdownModule
.metameta: {
slug: string;
title: string;
frontmatter: Record<string, unknown>;
tableOfContents: TocLevel[];
tableOfContentsFlat: Heading[];
}
.titletitle: string
}),
templatetemplate?: BodyTemplate<{
url: URL;
props: {
GET: undefined;
POST: {
success: boolean;
message: "Wrong input!";
};
};
params: Parameters;
}> | undefined
A function that returns a server only or a Lit client hydratable template.
Route context is provided at runtime during the build.
: async (contextcontext: {
url: URL;
props: {
GET: undefined;
POST: {
success: boolean;
message: "Wrong input!";
};
};
params: Parameters;
}
) => {
const initialDataconst initialData: {
foo: string;
}
= { foofoo?: string | undefined
: 'bar' } satisfies MyElementclass MyElement
['initialData'];
const achievementsconst achievements: Achievement[]
= await dbconst db: {
query<T>(str: string): T[];
}
Dummy DB handler…
.queryquery<Achievement>(str: string): Achievement[]
<Achievementtype Achievement = {
name: string;
date: Date;
}
>(
sqlfunction sql(str: string): string
Dummy SQL template literal…
`SELECT * FROM achievements`,
);
return htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>
Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html
tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`
<h1>${homeReadmeconst homeReadme: MarkdownModule
.metameta: {
slug: string;
title: string;
frontmatter: Record<string, unknown>;
tableOfContents: TocLevel[];
tableOfContentsFlat: Heading[];
}
.titletitle: string
}</h1>
<main>
<article>${homeReadmeconst homeReadme: MarkdownModule
.bodybody: {
html: string;
lit: TemplateResult;
}
.litlit: TemplateResult
}</article>
</main>
<aside>
<form method="post">
<input type="text" name="achievement" />
<button>Add achievement</button>
<span>${contextcontext: {
url: URL;
props: {
GET: undefined;
POST: {
success: boolean;
message: "Wrong input!";
};
};
params: Parameters;
}
.propsprops: {
GET: undefined;
POST: {
success: boolean;
message: "Wrong input!";
};
}
.POSTtype POST: {
success: boolean;
message: "Wrong input!";
}
?.messagemessage: "Wrong input!"
}</span>
</form>
<my-element initialData=${JSONvar JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
.stringifyJSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)
Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
(initialDataconst initialData: {
foo: string;
}
)}></my-element>
<my-client-only-element></my-client-only-element>
<footer>
${achievementsconst achievements: Achievement[]
.mapArray<Achievement>.map<TemplateResult<1>>(callbackfn: (value: Achievement, index: number, array: Achievement[]) => TemplateResult<1>, thisArg?: any): TemplateResult<...>[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
(
(achievementachievement: Achievement
) =>
htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>
Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html
tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`<section class=${`achievement-${achievementachievement: Achievement
.namename: string
}`}>
<h1>${achievementachievement: Achievement
.namename: string
}</h1>
<p>${achievementachievement: Achievement
.datedate: Date
}</p>
</section>`,
)}
</footer>
</aside>
<footer>
<small>You are visiting ${contextcontext: {
url: URL;
props: {
GET: undefined;
POST: {
success: boolean;
message: "Wrong input!";
};
};
params: Parameters;
}
.urlurl: URL
.hrefURL.href: string
}</small>
</footer>
`;
},
});
📄 /src/routes/index.client.ts
Importing your components in this page's client bundle entrypoint will make the server markup alive.
requestIdleCallbackfunction requestIdleCallback(callback: IdleRequestCallback, options?: IdleRequestOptions): number
(() => import('../features/my-element.js'));
// ...
Don't import on server-side, if you want a client-only element.
import '../features/my-client-only-element.js';
consolevar console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without importing the node:console
module.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
.logConsole.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
('Welcome', navigatorvar navigator: Navigator
.userAgentNavigatorID.userAgent: string
);
// ...
📄 /src/features/my-element.ts
import { LitElementclass LitElement
Base element class that manages element properties and attributes, and
renders a lit-html template.
To define a component, subclass LitElement
and implement a
render
method to provide the component's template. Define properties
using the
{@linkcode
LitElement.properties
properties
}
property or the
{@linkcode
property
}
decorator.
, cssconst css: (strings: TemplateStringsArray, ...values: (CSSResultGroup | number)[]) => CSSResult
A template literal tag which can be used with LitElement's
{@linkcode
LitElement.styles
}
property to set element styles.
For security reasons, only literal string values and number may be used in
embedded expressions. To incorporate non-literal values
{@linkcode
unsafeCSS
}
may be used inside an expression.
, htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>
Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html
tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
} from 'lit';
import { customElementconst customElement: (tagName: string) => CustomElementDecorator
Class decorator factory that defines the decorated class as a custom element.
, propertyfunction property(options?: PropertyDeclaration): PropertyDecorator
A class field or accessor decorator which creates a reactive property that
reflects a corresponding attribute value. When a decorated property is set
the element will update and render. A
{@linkcode
PropertyDeclaration
}
may
optionally be supplied to configure property features.
This decorator should only be used for public fields. As public fields,
properties should be considered as primarily settable by element users,
either via attribute or the property itself.
Generally, properties that are changed by the element should be private or
protected fields and should use the
{@linkcode
state
}
decorator.
However, sometimes element code does need to set a public property. This
should typically only be done in response to user interaction, and an event
should be fired informing the user; for example, a checkbox sets its
checked
property when clicked and fires a changed
event. Mutating public
properties should typically not be done for non-primitive (object or array)
properties. In other cases when an element needs to manage state, a private
property decorated via the
{@linkcode
state
}
decorator should be used. When
needed, state properties can be initialized via public properties to
facilitate complex interactions.
class MyElement {
} from 'lit/decorators.js';
import { styleMapconst styleMap: (styleInfo: Readonly<StyleInfo>) => import("../directive.js").DirectiveResult<typeof StyleMapDirective>
A directive that applies CSS properties to an element.
styleMap
can only be used in the style
attribute and must be the only
expression in the attribute. It takes the property names in the
{@link
StyleInfo
styleInfo
}
object and adds the properties to the inline
style of the element.
Property names with dashes (-
) are assumed to be valid CSS
property names and set on the element's style object using setProperty()
.
Names without dashes are assumed to be camelCased JavaScript property names
and set on the element's style object using property assignment, allowing the
style object to translate JavaScript-style names to CSS property names.
For example styleMap({backgroundColor: 'red', 'border-top': '5px', '--size': '0'})
sets the background-color
, border-top
and --size
properties.
} from 'lit/directives/style-map.js';
@customElementfunction customElement(tagName: string): CustomElementDecorator
Class decorator factory that defines the decorated class as a custom element.
('my-element')
export class MyElementclass MyElement
extends LitElementclass LitElement
Base element class that manages element properties and attributes, and
renders a lit-html template.
To define a component, subclass LitElement
and implement a
render
method to provide the component's template. Define properties
using the
{@linkcode
LitElement.properties
properties
}
property or the
{@linkcode
property
}
decorator.
{
static readonly GREETINGMyElement.GREETING: "Hello"
= 'Hello';
@propertyfunction property(options?: PropertyDeclaration<unknown, unknown> | undefined): PropertyDecorator
A class field or accessor decorator which creates a reactive property that
reflects a corresponding attribute value. When a decorated property is set
the element will update and render. A
{@linkcode
PropertyDeclaration
}
may
optionally be supplied to configure property features.
This decorator should only be used for public fields. As public fields,
properties should be considered as primarily settable by element users,
either via attribute or the property itself.
Generally, properties that are changed by the element should be private or
protected fields and should use the
{@linkcode
state
}
decorator.
However, sometimes element code does need to set a public property. This
should typically only be done in response to user interaction, and an event
should be fired informing the user; for example, a checkbox sets its
checked
property when clicked and fires a changed
event. Mutating public
properties should typically not be done for non-primitive (object or array)
properties. In other cases when an element needs to manage state, a private
property decorated via the
{@linkcode
state
}
decorator should be used. When
needed, state properties can be initialized via public properties to
facilitate complex interactions.
class MyElement {
({ typePropertyDeclaration<unknown, unknown>.type?: unknown
Indicates the type of the property. This is used only as a hint for the
converter
to determine how to convert the attribute
to/from a property.
: Objectvar Object: ObjectConstructor
Provides functionality common to all JavaScript objects.
}) initialDataMyElement.initialData: {
foo?: string;
}
: { foofoo?: string | undefined
?: string } = {};
@propertyfunction property(options?: PropertyDeclaration<unknown, unknown> | undefined): PropertyDecorator
A class field or accessor decorator which creates a reactive property that
reflects a corresponding attribute value. When a decorated property is set
the element will update and render. A
{@linkcode
PropertyDeclaration
}
may
optionally be supplied to configure property features.
This decorator should only be used for public fields. As public fields,
properties should be considered as primarily settable by element users,
either via attribute or the property itself.
Generally, properties that are changed by the element should be private or
protected fields and should use the
{@linkcode
state
}
decorator.
However, sometimes element code does need to set a public property. This
should typically only be done in response to user interaction, and an event
should be fired informing the user; for example, a checkbox sets its
checked
property when clicked and fires a changed
event. Mutating public
properties should typically not be done for non-primitive (object or array)
properties. In other cases when an element needs to manage state, a private
property decorated via the
{@linkcode
state
}
decorator should be used. When
needed, state properties can be initialized via public properties to
facilitate complex interactions.
class MyElement {
({ typePropertyDeclaration<unknown, unknown>.type?: unknown
Indicates the type of the property. This is used only as a hint for the
converter
to determine how to convert the attribute
to/from a property.
: Numbervar Number: NumberConstructor
An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
}) bgTintMyElement.bgTint: number
= 0.5;
renderMyElement.render(): TemplateResult<1>
Invoked on each update to perform rendering tasks. This method may return
any value renderable by lit-html's ChildPart
- typically a
TemplateResult
. Setting properties inside this method will not trigger
the element to update.
() {
return htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>
Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html
tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`
<div
@click=${() => (this.bgTintMyElement.bgTint: number
= Mathvar Math: Math
An intrinsic object that provides basic mathematics functionality and constants.
.randomMath.random(): number
Returns a pseudorandom number between 0 and 1.
())}
style=${styleMapfunction styleMap(styleInfo: Readonly<StyleInfo>): DirectiveResult<typeof StyleMapDirective>
A directive that applies CSS properties to an element.
styleMap
can only be used in the style
attribute and must be the only
expression in the attribute. It takes the property names in the
{@link
StyleInfo
styleInfo
}
object and adds the properties to the inline
style of the element.
Property names with dashes (-
) are assumed to be valid CSS
property names and set on the element's style object using setProperty()
.
Names without dashes are assumed to be camelCased JavaScript property names
and set on the element's style object using property assignment, allowing the
style object to translate JavaScript-style names to CSS property names.
For example styleMap({backgroundColor: 'red', 'border-top': '5px', '--size': '0'})
sets the background-color
, border-top
and --size
properties.
({ '--bg-tint': this.bgTintMyElement.bgTint: number
})}
>
${this.initialDataMyElement.initialData: {
foo?: string | undefined;
}
.foofoo?: string | undefined
} - ${MyElementclass MyElement
.GREETINGMyElement.GREETING: "Hello"
}
</div>
`;
}
static stylesMyElement.styles: CSSResult[]
Array of styles to apply to the element. The styles should be defined
using the
{@linkcode
css
}
tag function, via constructible stylesheets, or
imported from native CSS module scripts.
Note on Content Security Policy:
Element styles are implemented with <style>
tags when the browser doesn't
support adopted StyleSheets. To use such <style>
tags with the style-src
CSP directive, the style-src value must either include 'unsafe-inline' or
nonce-<base64-value>
with <base64-value>
replaced be a server-generated
nonce.
To provide a nonce to use on generated <style>
elements, set
window.litNonce
to a server-generated nonce in your page's HTML, before
loading application code:
<script>
// Generated and unique per request:
window.litNonce = 'a1b2c3d4';
</script>
= [
cssconst css: (strings: TemplateStringsArray, ...values: (CSSResultGroup | number)[]) => CSSResult
A template literal tag which can be used with LitElement's
{@linkcode
LitElement.styles
}
property to set element styles.
For security reasons, only literal string values and number may be used in
embedded expressions. To incorporate non-literal values
{@linkcode
unsafeCSS
}
may be used inside an expression.
`
:host {
display: block;
margin: 1rem;
}
div {
background: hsl(calc(var(--bg-tint, 0) * 360), 50%, 50%);
}
`,
];
}
Highlights
Standards oriented
Built with a platform-minded philosophy. Every time a standard can be leveraged
for a task, it should be.
It also means fewer vendor-specific idioms to churn on and a more portable
codebase overall.
Stop re-implementing the wheel, and embrace future-proof APIs, you’ll thank
yourself later!
Light and unobtrusive
All in all, the Gracile framework is just Vite, Lit SSR and a very restricted set of helpers and third parties.
Check its dependency tree on npmgraph, you’ll see by yourself.
Also, everything is done to keep your Vite configuration as pristine as possible. Augmenting an existing project can be done in a pinch, with no interference.