- Requirements
- Getting Started
- Directory Structure
- Managing Multiple Websites
- Pages
- Front Matter
- Color Scheme
- Stylesheets
- JavaScript files
- Images
- Animations
- Audios
- Fonts and Icons
- Layouts
- Wrappers
- Segments
- Components
- Articles
- What can be embedded in what?
- Links
- Link list
- Dynamic Elements
- Embedding Teamgenik Mini-apps
- Site Properties and Property Inheritance
- Managing the Contents of the
<head>
Element - List of special tags and attributes
- TODO List
- License
- Node.js: 20 or higher
- npm: 8.0 or higher
The first thing to do is create a working directory for tgweb. Its location can be anywhere.
As an example, let's say you create a subdirectory called my_site
under your home directory
and choose that as your working directory:
mkdir -p ~/my_site
Then go in and install tgweb with npm
:
cd my_site
npm install tgweb@latest
In your working directory you will have a directory named node_modules
, where you will find
tgweb and its dependencies installed.
To begin creating a website using tgweb, run the following command in your working directory:
npx tgweb-init
This command creates two subdirectories, src
and dist
, and several files.
Initially, index.html
in the src/pages
directory has the following contents:
<body>
<p class="p-2 text-red-500">Hello, world</p>
</body>
Notice that the body
element is not surrounded by <html>
and </html>
.
To start the tgweb server, run the following command:
npx tgweb-server
This command tries to start the tgweb server using port 3000. If this port is already in use, the following error message will appear:
ERROR: Could not start a web server. Port 3000 is in use.
If you want to use a different port, specify it in the PORT
environment variable as follows:
PORT=4000 npx tgweb-server
When the tgweb server starts successfully, the following message is displayed:
tailwindcss began to monitor the HTML files for changes.
Web server is listening on port 3000.
Rebuilding tailwind.css. Done in 761ms.
Then, open http://localhost:3000
with your browser.
Make sure that the text "Hello, world!" appears with the red text color.
The class tokens text-red-500
and p-4
specified in the class
attribute of the <p>
element are provided by Tailwind CSS.
The token text-red-500
specifies coral red (#ef4444
) as the text color of <p>
element,
and the token p-4
sets the padding of the <p>
element to scale 4 (16px/1rem).
Also verify that a file named index.html
has been created in the dist
directory.
The contents of that file should be a complete HTML document that contains
the <html>
and <head>
elements.
In addition, tgweb generates a file named tailwind.css
under the dist/css
directory
with the help of Tailwind CSS.
You may have noticed that the content of the <title>
element of index.html
is "No Title".
How to deal with this problem is described later.
Edit index.html
as follows:
<body>
<p class="p-2 text-green-500">Hello, world</p>
</body>
Confirm that the browser screen is automatically redrawn and the text color of "Hello, world!" changes from red to green.
Presse Ctrl + C
to stop the tgweb server.
To generate the website data for distribution, run the following command:
npx tgweb-dist
Running npx tgweb-init
from the command line creates a directory structure with the
following elements:
.
├── dist
├── node_modules
├── src
│ ├── animations
│ ├── articles
│ ├── audios
│ ├── components
│ ├── images
│ ├── layouts
│ ├── pages
│ │ └── index.html
│ ├── segments
│ ├── tags
│ ├── color_scheme.toml
│ └── site.toml
├── package-lock.json
└── package.json
Please note the following:
- tgweb scans the contents of the
src
directory, generates HTML files and writes them into thedist
directory. - tgweb scans the contents of the
src
directory, copies image and audio files to thedist
directory. - Tailwind CSS scans the HTML files of the
dist
directory, generates a CSS filetailwind.css
on thedist/css
directory. - It makes no sense for the user to rewrite the contents of the
dist
directory. - Users are not allowed to add their own CSS files to the website.
- Users are not allowed to add their own javascript files to the website.
In the Getting Started, we installed tgweb in a working directory and built a single website there. However, it is possible to manage multiple websites under a working directory.
As an example, let's assume the web
directory under the home directory is the working directory.
We will proceed in the same order as when building a single website up to the point where we
install tgweb with npm.
The procedure is the same as when building a single website, up to the point where tgweb is
installed with npm
:
mkdir ~/web
cd web
npm install tgweb@latest
Now choose a subdirectory name for the first website you will create in this working directory.
Let's call it site_0
as an example. Then run the following command:
npx tgweb-init site_0
This command creates a sites
subdirectory, and then a site_0
subdirectory under that. It then
creates two subdirectories, src
and dist
, and some files under the site_0
subdirectory.
When a working directory has the sites
subdirectory, we say that it has a
multi-site composition. Conversely, when a working directory does not have the sites
subdirectory, we say that the it has a single-site composition.
You can see that tgweb initially generate index.html
in the web/sites/site_0/src/pages
directory with the following contents:
<body>
<p class="p-2 text-red-500">Hello, world</p>
</body>
Then, you can start the tgweb server by executing the following command:
npx tgweb-server site_0
Thus, by specifying a subdirectory name as the argument to the npx tgweb-init
and
npx tgweb-server
commands, multiple websites can be managed under a single working directory.
If this approach is adopted, the structure of the working directory will look like this:
.
├── node_modules
├── package-lock.json
├── package.json
└── sites
├── site_0
│ ├── dist
│ └── src
│ ├── animations
│ ├── articles
│ ├── audios
│ ├── color_scheme.toml
│ ├── components
│ ├── images
│ ├── layouts
│ ├── pages
│ │ └── index.html
│ ├── segments
│ ├── site.toml
│ └── tags
└── site_1
├── dist
└── src
├── animations
├── articles
├── audios
├── color_scheme.toml
├── components
├── images
├── layouts
├── pages
│ └── index.html
├── segments
├── site.toml
└── tags
You may start the tgweb server with the command with an argument including sites/
as follows:
npx tgweb-server sites/site_0
This will increase the number of characters of the command, but may make it easier to type the command with the help of shell completion.
To generate data for distribution of a website located in the subdirectory sites/_site_0
,
execute the following command:
npx tgweb-dist site_0
Switching to a multi-site composition
To make a working directory that has a single-site composition to have a multi-site one,
execute the following commands replacing site_0
with any subdirectory name:
mkdir -p sites/site_0
mv dist src tailwind.* sites/site_0
Conversely, to make a working directory that has a multi-site composition to have a single-site one, execute the following commands:
mv sites/site_0/* .
rm -rf sites
Note that if there are multiple subdirectories in the sites
directory, this operation will
delete all subdirectories except for site_0
.
The npx tgweb-archive
command can be used to create a zipped file of a src
directory.
In the case of a single-site structure, executing this command on a working directory will create
an archive file in the current directory with the name of the working directory plus the .zip
extension.
For a multi-site structure, specify the directory name of the target website on the working
directory like npx tgweb-archive site_0
. As a result, an archive file will be created in the
current directory with the name of the target directory plus the .zip
extension.
In tgweb, the HTML documents that make up a website are generated from a combination of template files. A page is a type of such template file.
Non-page templates include layouts, segments, wrappers, articles, and components, which are described in turn below.
Pages are placed in the src/pages
subdirectory under the working directory.
It is possible to create a subdirectory under the src/pages
directory and place pages under it.
However, it is not possible to create a subdirectory directly under the src/pages
directory with
the following names:
animations
articles
audios
images
tags
videos
Every website must have a file named index.html
in the src/pages
directory.
From this page, the home page of the website is generated.
Pages to which a layout is not applied are called "simple pages".
The following is an example of a simple page:
<body>
<h1 class="text-2xl font-bold">Greeting</h1>
<div class="bg-green-300 p-4">
<p>Hello, world!</p>
</div>
</body>
Note that the top-level element of a page should be the <body>
element.
Unlike normal HTML files, a tgweb page is not enclosed in <html>
and </html>
tags and does
not have a <head>
element.
A page will be converted into a complete HTML document and written to the dist
directory.
For example, src/pages/index.html
is converted to dist/index.html
and
src/pages/shops/new_york.html
is converted to dist/shops/new_york.html
.
Change src/pages/index.html
to the contents of the simple page above.
dist/index.html
generates a complete HTML document including the following <html>
and <head>
elements.
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<body>
<h1 class="text-2xl font-bold">Greeting</h1>
<div class="bg-green-300 p-4">
<p>Hello, world!</p>
</div>
</body>
</body>
</html>
The content of the <head>
element is automatically generated.
See below for details.
To open this page in a browser, specify http://localhost:3000
as the URL.
When a template begins with a line that consists only of ---
and there is another such line in
the template, the area enclosed by these two lines is called the front matter block.
In this area you can give values to a set of properties. This set of property/value pairs is called front matter.
Front matter blocks are written in TOML format. Shown next is an example of a front matter block:
---
[main]
title = "Our Mission"
[data]
current_year = 2023
[style]
red-box = "rounded border border-red-600 p-1 md:p-2"
# TODO: Needs to be checked by the boss.
---
In the example above, the four main components of the front matter block are used.
- Property definition
- Section header
- Comment
- Blank line
Lines beginning with a #
sign are ignored as comments. Blank lines are also ignored.
When a file exists whose name is the body of the template file name plus the extension .toml
,
the contents of that file are interpreted as the front matter of the template file.
The file is called front matter file.
For example, if index.html
and index.toml
exist in the src/pages
directory,
the latter is the front matter file of the former.
If a template file has both a front matter block and a front matter file, the front matter file takes precedence.
The line title = "Our Mission"
is an example of a property definition.
The title
to the left of the equals sign is the name of the property and the "Our Mission"
to
the right is the value of the property.
The property name, equals sign, and value must be on the same line, though some values can be broken over multiple lines.
If the property name consists only of uppercase and lowercase letters, numbers, underscores, and minus signs, it can be written without quotation marks; otherwise, the name must be enclosed in quotation marks, as in the following example.
"&_p" = "mb-2"
Property values are described in different ways depending on their type.
Strings must always be quoted.
Integers and floating point numbers should be unquoted, such as 100
, -16
, and 3.14
.
Booleans (true
and false
) are also not quoted and should be lowercase.
Other writing styles will be explained when examples appear.
In the previous example, the lines labeled [data]
and [style]
are called section headers. TOML calls them table headers, while Tgweb calls them section headers.
A section header marks the beginning of a section. The section continues until the next section header or until the end of the file.
The following section names are available in the front matter block:
- main
- data
- style
- meta.name
- meta.http-equiv
- meta.property
- link
The first three are described here; the other four are described in
Managing the Contents of the <head>
Element.
In the main section of the front matter block, set the value of predefined properties.
The following are examples of predefined properties:
scheme
: The scheme of the URL of the HTML document. It must behttp
orhttps
. Default:http
.host
: The host name of the URL of the HTML document. Default:localhost
.port
: The port number of the URL of the HTML document. Default: 3000.url
: The URL of the HTML document.root-url
: The root URL of the HTML document.title
: The title of the HTML document.layout
: The name of layout to be applied to the template. See Layouts.html-class
: The value set to theclass
attribute of the<html>
element.
Normally, it is not necessary to specify values for the scheme
, host
, and port
properties.
The values of these properties will be set appropriately when the website is published on
Teamgenik.
The value of the url
property is generated from these properties and the path to the page or
article. Its value is readonly.
The following three properties are meaningful only in articles:
index
: An integer value used to sort the articles.tags
: A single string or array of strings to classify articles.embedded-only
: A boolean value that determines whether a separate HTML document is created from an article; iftrue
, it will not be created. Default:false
.
See Articles for details.
The shared-wrapper
property is meaningful only in wrappers.
See Shared wrappers for details.
The draft
property indicates that pages and articles are drafts.
The tgweb-dist
and tgweb-server
commands ignore draft pages and articles.
Also, the <tg:article>
and <tg:articles>
elements will not embed draft pages and articles.
Also, the <tg:link>
and <tg:links>
elements will not generate links to draft pages and articles.
However, if you invoke the tgweb-dist
and tgweb-server
commands with the --buildDrafts
option, they will treat draft pages and articles as normal pages and articles.
The value of a predefined property can be embedded into a template by the <tg:prop>
element.
---
[main]
title = "Our Mission"
---
<body>
<h1 class="text-2xl font-bold">
<tg:prop name="title"></tg:prop>
</h1>
<div class="bg-green-300 p-4">
<p>Hello, world!</p>
</div>
</body>
As already noted, [data]
in the front matter block indicates the beginning of the data section.
Within the data section, custom properties can be defined. Website authors can set values for custom properties of any name. The value of a custom property must be a string or a number in decimal notation.
The value of a custom property can be embedded into a template by the <tg:data>
element.
---
[main]
title = "Our Mission"
[data]
message = "Hello, world!"
year = 2023
---
<body>
<h1 class="text-2xl font-bold">
<tg:prop name="title"></tg:prop>
</h1>
<div class="bg-green-300 p-4">
<p><tg:data name="message"></tg:data></p>
</div>
<footer>© Example Inc. <tg:data name="year"></tg:prop></footer>
</body>
You can also use the ${...}
notation to embed the value of a custom property into the attribute
value of an HTML element.
---
[data]
div-id = "special"
---
<body>
<div id="${div-id}">...</div>
</body>
However, the ${...}
notation does not make sense for class
attributes;
the method of embedding property values in class
attributes will be explained shortly.
A property defined in the "style" section can be used to give an alias to a set of class tokens. We call this a style alias. The value of a style alias must always be a string.
To embed a style alias defined as such into the value of a class
attribute, use the tg:class
attribute.
---
[style]
blue-square = "w-24 h-24 md:w-48 md:h-48 bg-blue-500 rounded-xl p-8 m-4"
---
<body>
<div tg:class="blue-square">
Hello, world!
</div>
</body>
Expanding the alias contained in the above template, we get the following result:
<body>
<div class="w-24 h-24 md:w-48 md:h-48 bg-blue-500 rounded-xl p-8 m-4">
Hello, world!
</div>
</body>
A long sequence of class tokens can be written over several lines surrounded by three quotation marks.
---
[style]
card = """
bg-white dark:bg-slate-900 rounded-lg px-6 py-8
ring-1 ring-slate-900/5 shadow-xl
"""
---
<body>
<div tg:class="card">
Hello, world!
</div>
</body>
The three consecutive double quotation marks ("""
) indicate the beginning and end of a
multi-line string.
Before being embedded in the class
attribute of an HTML element, the value of the property is
converted as follows:
- Any included newline characters are replaced by spaces.
- Consecutive leading and trailing spaces are removed.
- Consecutive spaces are replaced by a single space.
If an element has both the class
and tg:class
attributes, the combined value of both become
the final class
attribute value.
You may want to define the following style aliases using Tailwind CSS modifiers to achieve the responsive design.
box = """
w-16 h-16 p-1 border border-1 rounded
md:w-32 md:h-32 md:p-2 md:rounded-md
lg:w-48 md:h-48 lg:p-3 lg:rounded-lg
"""
If you are bothered by the repetition of modifiers such as md:
and lg:
, remove them using the
{...}
notation as follows:
box = """
w-16 h-16 p-1 border border-1 rounded
md { w-32 h-32 p-2 rounded-md }
lg { w-48 h-48 p-3 rounded-lg }
"""
The {...}
notation is especially useful for utilization of
arbitrary variants.
Assume that you have created the following style alias blog-article
:
blog-article = """
text-gray-900
[&_*]:mb-2
[&_*:last-child]:mb-0
[&_h2]:text-xl
[&_h2]:font-bold
[&_h2]:tracking-wide
[&_h2]:capitalize
[&_p]:font-serif
[&_p]:leading-5
"""
Then you can use this as follows:
<article tg:class="blog-article">
<h2>Title</h2>
<p>Lorem ipsum dolor sit amet.</p>
<p>Consectetur adipiscing elit.</p>
<p>Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</article>
The following use of {...}
notation eliminates the repetition of [&_h2]
and [&_p]
in the
above example.
blog-article = """
text-gray-900
[&_*]:mb-2
[&_*:last-child]:mb-0
[&_h2] { text-xl font-bold tracking-wide capitalize }
[&_p] { font-serif leading-5 }
"""
If no value is set for a particular property in the front matter of the page, search for wrappers and layout front matter,and site properties. For more information, see Site Properties and Property Inheritance.
Editing the color_scheme.toml
in the src
directory allows you to define custom color names
for Tailwind CSS.
A custom color name is a combination of a palette and a modifier.
The palette is a three-letter alphabet and the modifier is a one-letter alphabet.
The palette and modifier are joined by a minus sign, like bas-s
or neu-d
.
The following is a list of available palettes and their expected uses.
- bas: Base color (background color of entire website)
- neu: Neutral color (a quiet color like gray, beige, ivory, etc.)
- pri: Primary color (most frequently used color other than base and neutral colors)
- sec: Secondary color (second most frequently used color other than base and neutral colors)
- acc: Accent color (third most frequently used color other than base and neutral colors)
- nav: Navigation color (background color of navigation bar or sidebar)
The following is a list of modifiers and their expected meaning.
- s: Standard
- b: Brighter
- d: Darker
- c: Contrasting
Here, "contrasting" means a color with good visibility when text is drawn in that color against a standard-color background.
src/color_scheme.toml
bas-s = "#3d4451"
bas-c = "#a0aec0"
pri-s = "#45ba9f"
sec-s = "#70365d"
The defined custom color names can be used as the names of the colors that make up the Tailwind
CSS classes. For example, to set the color defined as pri-s
as the background color of an
element, specify the bg-pri-s
class in its class
attribute.
You can also use the color names provided by daisyUI, such as primary
,
secondary
, success
, warning
.
For more information, see Colors in the daisyUI Documentation.
Please note that at this time, tgweb does not support the switching of the daisyUI themes.
tgweb recommends using Tailwind CSS to fine-tune the visual design.
If you want to use your own CSS files, place the .css
files in the shared_css
or css
directory and add an entry to the front matter stylesheets
with the filename body as key and
value as true
.
The following is an example of front matter configuration:
[stylesheets]
main = true
The above setting means that main.css
in the shared_css
directory or the css
directory will be used.
Usually, the shared_css
directory is a symbolic link to ../../../shared_css
. If the same file name exists in the shared_css
directory and the css
directory, the one in the css
directory takes precedence.
If you import a .zip
file created with the npx tgweb-archive
command into TeamGenik,
the "stylesheets" section in site.toml
and frontmatter will be ignored.
This means that you cannot use your own CSS files on websites managed and published on TeamGenik.
tgweb recommends using [Dynamic elements](#dynamic elements) to add movement to on-screen elements.
If you want to use your own JavaScript files, place the .js
files in the shared_js
or js
directory and add an entry to the front matter javascripts
with the filename body as key and
value as true
.
The following is an example of front matter configuration:
[javascripts]
main = true
The above setting means that main.js
in the shared_js
directory or the js
directory will be
used.
The shared_js
directory is usually a symbolic link to ../../../shared_js
.
If the same file name exists in the shared_js
directory and the js
directory,
the one in the js
directory takes precedence.
If you import a .zip
file created with the npx tgweb-archive
command into TeamGenik,
the "scripts" section in site.toml
and frontmatter will be ignored.
This means that you cannot use your own JavaScript files in websites that you manage and publish on TeamGenik.
Image files are placed in the src/images
subdirectory under the working directory.
There are two ways to embed images in a page.
One is to use the <img>
element, and the other is to set it as the background of an element.
Teamgenik supports image files in the following formats:
- AVIF ('.avif')
- BMP ('.bmp')
- GIF ('.gif')
- JPEG ('.jpg', '.jpeg')
- PNG ('.png')
- WEBP ('.webp')
If the src
attribute of the <img>
element contains the absolute path of the image file,
the image will be embedded in the page.
<img src="/images/smile.png" alt="Smile face">
You do not need to write the width
and height
attributes in the <img>
tag,
because Teamgenik will automatically specify them for you.
Note that the value of the src
attribute is the absolute path of the image file.
When your website is published on Teamgenik, the values of the src
attribute of the <img>
elements will be converted appropriately.
Note also that Teamgenik does not allow the <img>
element to reference an external URL.
If you want to resize an image to fit the size of the image container, use the following class tokens provided by Tailwind CSS:
object-cover
: resizes an image to cover its containerobject-contain
: resizes an image to stay contained within its containerobject-fill
: stretches an image to fit its containerobject-scale-down
: display an image at its original size but scale it down to fit its container if necessary
For more information, see Object Fit of the Tailwind CSS Documentation.
You can embed images/smile.png
as a background image for a <div>
element by writing like this:
<div class="bg-[url(../images/smile.png)]"></div>
Tailwind CSS will detect this class
attribute and write the appropriate CSS fragment to
dist/css/tailwind.css
.
Note that inside the parentheses is the relative path from css/tailwind.css
to the image file.
Even if you embed src/images/smile.png
into src/pages/foo/bar.html
,
specify ../images/smile.png
instead of ../../images/smile.png
inside the parentheses
if you embed it as a background image.
If you want to adjust the rendering of a background image, use the following class tokens provided by Tailwind CSS:
bg-center
: centers the background image on the background layerbg-repeat
: repeats the background image both vertically and horizontallybg-repeat-x
: repeats the background image horizontallybg-repeat-y
: repeats the background image verticallybg-cover
: scales the background image until it fills the background layerbg-contain
: scales the background image to the outer edges without cropping or stretching
For more information, see Background Position, Background Repeat and Background Size of the Tailwind CSS Documentation.
The <tg:animation>
element allows you to embed an animation in a web page.
The animation is played by specifying the name of the animation file in its src
attribute.
The following example plays the animation file cat.json
, which is located in
the src/animations
subdirectory.
<tg:animation src="cat.json"></tg:animation>
The following example plays the animation file flag.lottie
,
located in the src/animations/common
subdirectory.
<tg:animation src="common/flag.lottie"></tg:animation>
By default the animation is 300px wide and 150px high.
To adjust the width and height, specify integer values for the width
and height
attributes
for the <tg:animation>
element.
<tg:animation src="cat.json" width="100" height="100"></tg:animation>
To adjust the animation's background color, etc., specify the class
attribute
for the <tg:animation>
element.
<tg:animation src="cat.json" class="bg-red-200"></tg:animation>
The <tg:animation>
element can also have the following attributes:
loop
: boolean indicating whether the animation should loop (default:"true"
).autoplay
: a boolean indicating whether the animation should start automatically (default:"true"
).click
: boolean indicating whether to toggle the playback state on click or tap (default:"false"
).hover
: boolean indicating whether to play only during mouse hover (default:"false"
).
If both click
and hover
attribute values are "true"
, only the click
attribute is valid.
Also, if the autoplay
attribute is "true"
, the hover
attribute is ignored.
See LottieFiles for information on how to obtain or create animation files.
Audio files are placed in the src/audio
subdirectory under the working directory.
Teamgenik supports audio files in the following formats:
- AAC ('.m4a')
- MP3 ('.mp3')
- Ogg Vorbis (
.ogg
) - WAV (
.wav
)
You can embed a UI object to play an audio content with the <audio>
element.
There are two ways to construct the <audio>
element.
One is to specify the absolute path of the audio file in the src
attribute of the <audio>
element itself.
<audio controls src="/audios/theme.mp3">
<a href="/audios/theme.mp3">Download</a>
</audio>
The content of the <audio>
element will be shown when thw browser does not support the <audio>
element.
The other is to place one or more <source>
elements inside the <audio>
element and specify
the absolute path of the audio file in their src
attribute.
<audio controls>
<source src="/audios/theme.ogg" type="audio/ogg">
<source src="/audios/theme.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
Note that Teamgenik does not allow the <audio>
and <source>
elements to reference an external
URL.
By setting the value of the outlined
, rounded
and sharp
properties to true
in the
"font.material-symbols" section of sites.toml
, icon called
Material Symbols
provided by Google will be available on your website.
[font.material-symbols]
outlined = true
rounded = true
sharp = true
By writing in this way, all styles of Material Symbols will be available.
However, if only some of the styles are used, then the load on the website visitor should be
reduced by specifying false
for properties with unused style names.
[font.material-symbols]
outlined = false
rounded = false
sharp = true
Alternatively, the property itself with the unused style name may be removed from site.toml
.
[font.material-symbols]
sharp = true
Material Symbols provides four variables for adjusting its typeface. To specify them, write as following:
[font.material-symbols]
rounded = { fill = 1, wght = 200, grad = 0, opsz = 24 }
sharp = { fill = 0, wght = 300, grad = 200, opsz = 40 }
The fill
is a variable that controls whether or not fill is applied;
0 means "no fill" and 1 means "fill". The default value is 0.
The wght
controls the thickness (weight) of icons:
Possible values are 100, 200, 300, 400, 500, 600, or 700.
100 is the thinnest and 700 is the thickest.
The default value is 400.
The grad
is the variable that determines the grade of icons.
By changing the value of this variable, the thickness of the icon can be fine-tuned.
Possible values are -25, 0, or 200. The default value is 0.
A negative value makes icons thinner, a positive value makes them thicker.
The opsz
variable determines the optical size of icons, which is their recommended display
size. Possible values are 20, 24, 40, or 48. The default value is 24.
In general, larger values of optical size result in thinner lines, narrower spaces, and shorter
x-height (the distance between the baseline and the average line of the typeface's lowercase
letters).
If you want to have multiple variants of a single style with different values for the variables, add a minus sign and the variant name after the style name, as follows:
[font.material-symbols]
outlined = true
sharp = true
rounded = { fill = 0, wght = 200, grad = 0, opsz = 24 }
rounded-strong = { fill = 1, wght = 400, grad = 0, opsz = 24 }
rounded-bold = { fill = 0, wght = 700, grad = 0, opsz = 24 }
Note that only lowercase letters (a-z
) and numerals (0-9
) are allowed in variant names.
Material Symbols can be embedded in a template by specifying a class
beginning with
material-symbols-
for a <span>
element and placing the name of the symbol or code point
as its content.
When specifying a style with a variant name in the class
attribute, use a space instead of a minus sign between the style name and variant name.
The name of the symbol must be converted to a snake case. That is, replace all spaces in the name with underscores and all uppercase letters with lowercase ones.
The code point of a symbol is a four-digit hexadecimal number, such as e88a
.
When specified as the content of a <span>
element, it must be enclosed in &#x
and ;
like

You can find the code point of each symbol on the Material Symbols and Icons.
Although symbol names are easier to use, code points have one advantage.
Using a symbol name may temporarily disrupt the layout of a web page because a space the width of the symbol name is displayed in place of the symbol until the font file is downloaded. The use of code points can reduce the disruption to the layout.
The "Home" symbol (outlined):
<span class="material-symbols-outlined">home</span>
<span class="material-symbols-outlined"></span>
The "Delete" symbol (rounded):
<span class="material-symbols-rounded">delete</span>
<span class="material-symbols-rounded"></span>
The "Shopping Bag" symbol (sharp):
<span class="material-symbols-sharp">shopping_bag</span>
<span class="material-symbols-sharp"></span>
The "strong" variant of the "Star" symbol (rounded):
<span class="material-symbols-rounded strong">star</span>
<span class="material-symbols-rounded strong"></span>
The Roboto font family from Google Fonts can be used on your website
by setting the following in the "font.google-fonts" section of site.toml
.
Roboto = true
The following is an example of using the Robot font family:
<p class="font-['Roboto']">Hello, world!</p>
If the font family name contains spaces, the name must be enclosed in double quotes as follows:
"Noto Sans JP" = true
Also, when using it in an HTML template, spaces should be replaced with underscores.
<p class="font-['Noto_Sans_JP']">こんにちは、世界!</p>
To select some font weights in order to reduce the size of font file, specify weights as an array
instead of true
.
"Noto Sans JP" = [400, 800]
The following example uses the Noto Sans JP font family with weight 800.
<p class="font-['Noto_Sans_JP'] font-[800]">こんにちは、世界!</p>
To select font weights for each style, specify weights using the inline table as follows:
"Pathway Extreme" = { normal = [400, 800], italic = [400] }
The following example uses the italic Pathway Extreme font family with weight 400.
<p class="font-['Pathway_Extreme'] italic font-[400]">Hello, world!</p>
Typically, the pages of a website have a set of areas that share most of the same content: header, sidebar, footer, etc.
Separating this set of areas from the page as a single template makes the website easier to manage. We call this separated template a "layout".
Layouts are HTML files placed in the src/layouts
subdirectory under the working directory.
A layout must satisfy the following three conditions:
- There is only one top-level element.
- The top-level element is a
<body>
element. - The top-level element contains only one
<tg:content>
element within its descendant elements.
The <tg:content>
element indicates where in the layout the page will be inserted.
src/layouts/common.html
<body>
<header>
<div>Example</div>
</header>
<main>
<tg:content></tg:content>
</main>
<footer>© Example Inc. 2023</footer>
</body>
Note that you cannot write <tg:content />
instead of <tg:content></tg:content>
.
To apply this layout to a page, specify the name of the layout in the layout
property of the
the front matter of the page.
The name of the layout is the file name of the layout minus its extension (.html
).
In this case, common
is the name of the layout.
src/pages/index.html
---
[main]
layout = "common"
---
<h1>Welcome!</h1>
<div class="bg-green-300 p-4">
<p>Hello, world!</p>
</div>
When the layout common
shown in the previous example is applied to this page file,
the following HTML document is generated:
<html>
<head>
...
</head>
<body>
<header>
<div>Example</div>
</header>
<main>
<h1>Welcome!</h1>
<div class="bg-green-300 p-4">
<p>Hello, world!</p>
</div>
</main>
<footer>© Example Inc. 2023</footer>
</body>
</html>
The values of the properties set in the front matter of the page can be embedded in the layout
using the <tg:prop>
element.
src/layouts/common.html
<body>
<header>
<div>Example</div>
</header>
<main>
<h1 class="text-3xl"><tg:prop name="title"></tg:prop></h1>
<tg:content></tg:content>
</main>
</body>
src/pages/greeting.html
---
[main]
layout = "common"
title = "Greeting"
---
<h1>Welcome!</h1>
<div class="bg-green-300 p-4">
<p>Hello, world!</p>
</div>
The <tg:slot>
element is a place holder inside a layout that you can fill with a content
specified within a page.
A name
attribute must be specified for the <tg:slot>
element.
To embed content into a slot in a layout, place a <tg:insert>
element on the page where the
layout is to be applied.
If you specify the name of the slot as the value of the name
property of <tg:insert>
element,
the slot will be replaced with the element's content.
When page content is inserted into a <tg:content>
element in a layout,
all <tg:insert>
elements are removed from the page content.
src/layouts/product.html
<body class="p-2">
<tg:content></tg:content>
<div class="border-2 border-black border-solid p-2">
<tg:slot name="remarks"></tg:slot>
</div>
<div><tg:slot name="badges"></tg:slot></div>
</body>
src/pages/product1.html
---
[main]
layout = "product"
title = "Product 1"
---
<div>
<h1>Product 1</h1>
<p>Description</p>
</div>
<tg:insert name="remarks">This product is very <em>fragile</em>.</tg:insert>
<tg:insert name="badges"><span>A</span><span>B</span></tg:insert>
When the layout product
is applied to the page product1.html
,
the following HTML document is generated:
<html>
<head>
...
</head>
<body class="p-2">
<div>
<h1>Product 1</h1>
<p>Description</p>
</div>
<div class="border-2 border-black border-solid p-2">
This product is very <em>fragile</em>.
</div>
<div><span>A</span><span>B</span></div>
</body>
</html>
If the content to be inserted into the slot is not defined, the content of the <tg:slot>
element
is used as a fallback content.
src/layouts/message.html
<body class="p-2">
<tg:content></tg:content>
<div><tg:slot name="message">No message.</tg:slot></div>
</body>
src/pages/home.html
---
[main]
layout = "message"
title = "Home"
---
<h1>Home</h1>
<p>Hello, world!</p>
When the layout message
is applied to the page home.html
,
the following HTML document is generated:
<html>
<head>
...
</head>
<body class="p-2">
<h1>Home</h1>
<p>Hello, world!</p>
<div>No message.</div>
</body>
</html>
Normally, the <tg:if-complete>
element in a layout is simply replaced with its content.
However, if the following three conditions are not met, the entire element is deleted:
- The property values to be inserted for all
<tg:prop>
elements within it are defined. - The custom property values to be inserted for all
<tg:data>
elements within it are defined. - The contents to be inserted for all
<tg:slot>
elements within it are defined.
src/layouts/message.html
<body class="p-2">
<tg:content></tg:content>
<tg:if-complete>
<hr class="h-px my-8 bg-gray-200 border-0">
<div class="bg-gray-800 text-white p-4">To: <tg:data name="custom-name"></tg:data></div>
<div class="bg-gray-200 p-4"><tg:slot name="message"></tg:slot></div>
</tg:if-complete>
</body>
src/pages/home1.html
---
[main]
layout = "message"
[data]
custom-name = "Alice"
---
<h1>Home</h1>
<tg:insert name="message">Hello, world!</tg:insert>
When the layout message
is applied to the page home.html
,
the following HTML document is generated:
<html>
<head>
...
</head>
<body class="p-2">
<h1>Home</h1>
<hr class="h-px my-8 bg-gray-200 border-0">
<div class="bg-gray-800 text-white p-4">To: Alice</div>
<div class="bg-gray-200 p-4">Hello, world!</div>
</body>
</html>
src/pages/home2.html
---
[main]
layout = "message"
[data]
custom-name = "Alice"
---
<h1>Home</h1>
When the layout message
is applied to the page home2.html
,
the following HTML document is generated:
<html>
<head>
...
</head>
<body class="p-2">
<h1>Home</h1>
</body>
</html>
A wrapper is a template that exists at a level between layouts and pages.
Wrappers allow you to apply a common style and add common elements to a group of pages.
The file name of a wrapper is always _wrapper.html
and it is placed directly under the
src/pages
subdirectory of working directory or its descendant directories.
A wrapper placed in a directory applies to all pages in that directory.
If the wrapper does not exist in a directory, the first wrapper found in the directory hierarchy from the bottom up becomes the wrapper for that directory.
For example, if src/pages/foo/_wrapper.html
exists and _wrapper.html
does not exist in either
the src/pages/foo/bar
directory or the src/pages/foo/bar/baz
directory, then
src/pages/foo/_wrapper.html
will be the wrapper for src/pages/foo/bar/baz
directory.
Basically, the wrapper is written the same way as that of the layout.
The <tg:content>
element indicates where in the wrapper the page will be inserted.
src/pages/mission/_wrapper.html
<h1 class="text-xl bg-blue-400 p-2">Our Mission</h1>
<div class="[&_p]:mt-2 [&_p]:pl-2">
<tg:content></tg:content>
</div>
The class
attribute value [&_p]:mt-2 [&_p]:pl-2
sets the margin-top
and padding-left
of all <p>
elements within this
wrapper to scale 2 (8px/0.5rem). For the notation [&_p]
, see
Using arbitrary variants.
As already mentioned, a wrapper placed in a directory applies to all pages in that directory.
In this example, assume the following two pages exist in the src/pages/mission/
directory and its descendant directory src/pages/mission/member_mission/
.
src/pages/mission/team_mission.html
<h2 class="text-lg">[Team]</h2>
<div>
<p>Create workshop pages.</p>
</div>
src/pages/mission/member_mission/alice_mission.html
<h2 class="text-lg">[Alice]</h2>
<div>
<p>Coding with html (workshop page).</p>
<p>Coding with html (workshop calender page).</p>
</div>
Since the wrapper _wrapper.html
is applied to these two pages, each body
element is generated as follows.
dist/pages/mission/team_mission.html
<body>
<h1 class="text-xl bg-blue-400 p-2">Our Mission</h1>
<div class="[&_p]:mt-2 [&_p]:pl-2">
<h2 class="text-lg">[Team]</h2>
<div>
<p>Create workshop pages.</p>
</div>
</div>
</body>
dist/pages/mission/member_mission/alice_mission.html
<body>
<h1 class="text-xl bg-blue-400 p-2">Our Mission</h1>
<div class="[&_p]:mt-2 [&_p]:pl-2">
<h2 class="text-lg">[Alice]</h2>
<div>
<p>Coding with html (workshop page).</p>
<p>Coding with html (workshop calender page).</p>
</div>
</div>
</body>
For both pages, margin-top
and padding-left
of the <p>
element are set to scale 2 (8px/0.5rem).
In this way, common styles and common elements can be added to all pages in a particular directory and its descendant directories.
The value of a property set in the wrapper's front matter becomes the default value of the property for the page to which that wrapper is applied. If the same property has a value set in the front matter of the page, the setting in the page takes precedence.
Wrapper property values take precedence over site property values.
In this example, title = "Team's Mission"
is defined as the wrapper property value.
It is then described as the content of the h1
element using the <tg:prop>
element.
src/pages/mission/_wrapper.html
---
[main]
title = "Team's Mission"
---
<h1 class="text-xl bg-blue-400 p-2">
<tg:prop name="title"></tg:prop>
</h1>
<div class="[&_p]:mt-2 [&_p]:pl-2">
<tg:content></tg:content>
</div>
Apply the above wrapper to the following two pages.
src/pages/mission/team_mission.html
<div>
<p>Create workshop pages.</p>
</div>
src/pages/member_mission/alice_mission.html
---
[main]
title = "Alice's Mission"
---
<div>
<p>Coding with html (workshop page).</p>
<p>Coding with html (workshop calender page).</p>
</div>
The team_mission.html
one does not redefine the property title
in the front matter, but the member_mission/alice_mission.html
one does.
When the wrapper _wrapper.html
is applied to these two pages, each body
element is generated as follows.
dist/pages/mission/team_mission.html
<body>
<h1 class="text-xl bg-blue-400 p-2">
Team's Mission
</h1>
<div class="[&_p]:mt-2 [&_p]:pl-2">
<div>
<p>Create workshop pages.</p>
</div>
</div>
</body>
dist/pages/mission/member_mission/alice_mission.html
<body>
<h1 class="text-xl bg-blue-400 p-2">
Alice's Mission
</h1>
<div class="[&_p]:mt-2 [&_p]:pl-2">
<div>
<p>Coding with html (workshop page).</p>
<p>Coding with html (workshop calender page).</p>
</div>
</div>
</body>
team_mission.html
is rendered with the property title
defined in the wrapper.
On the other hand, member_mission/alice_mission.html
is rendered by title
, which is redefined in the page.
Wrappers that are shared among multiple websites are called shared wrappers.
Shared wrappers are placed in the src/shared_wrappers
subdirectory of the working directory.
Usually, this subdirectory is a symbolic link to another directory.
If you initialized your website working directory in the way described in
Managing Multiple Websites,
the src/shared_wrappers
subdirectory will created as a symbolic link referring to the
../../../shared_wrappers
directory.
If you want to use a common wrapper named "decorated.html"
as a wrapper for a certain directory,
Create a _wrapper.html
in that directory with the following contents:
---
[main]
shared-wrapper = "decorated"
---
A segment is a template file that can be embedded in pages, layouts or segments. Segments cannot be embedded in templates other than these types, such as articles, wrappers, and components.
When embedding a segment into another segment, care should be taken to avoid circular references. If a circular reference is detected, an error message will be inserted in the generated HTML.
Segments are placed in the src/segments
subdirectory of the working directory.
The following is an example of a segment:
src/segments/hero.html
<section class="bg-base-200 p-4 border border-rounded">
<div class="flex">
<img src="/images/hello.jpg">
<div>
<h1 class="text-5xl font-bold">Hello, world!</h1>
<p>The quick brown fox jumps over the lazy dog.</p>
</div>
</div>
</section>
To embed a segment into a page, add a <tg:segment>
element at the
location where you want to place it and specify its name in the name
attribute.
<div>
<tg:segment name="hero"></tg:segment>
<main>
...
</main>
</div>
You can pass custom properties to a segment using the <tg:segment>
element's data-*
attribute.
src/segment/hero.html
<section class="bg-base-200 p-4 border border-rounded">
<div class="flex">
<img src="/images/${image-path}">
</div>
</section>
src/pages/index.html
<div>
<tg:segment name="hero" data-image-path="hello.jpg"></tg:segment>
<main>
...
</main>
</div>
Like layouts, slots can be placed inside segments. The method of embedding content in the slots within a segment is similar to that of a layout.
src/segment/hero.html
<section class="bg-base-200 p-4 border border-rounded">
<div class="flex">
<img src="/images/hello.jpg">
<div>
<h1 class="text-5xl font-bold">Hello, world!</h1>
<tg:slot name="paragraph"></tg:slot>
</div>
</div>
</section>
src/pages/index.html
<div>
<tg:segment name="hero">
<tg:insert name="paragraph">
<p>The quick brown fox jumps over the lazy dog.</p>
</tg:insert>
</tg:segment>
<main>
...
</main>
</div>
The <tg:prop>
and <tg:data>
elements allow you to embed the value of a property in a segment.
---
[data]
message = "Hi!"
---
<div>
<tg:data name="message"></tg:data>
</div>
Note that the segment inherits the property from the page or layout in which it is embedded. When a property with the same name is defined in a segment and a page or layout, the value defined in the page or layout takes precedence.
A components is a template file that can be embedded in pages, segments, components, articles and layouts.
Components are placed in the src/components
subdirectory of the working directory.
The following is an example of a component:
src/components/smile.html
<span class="inline-block border-solid border-2 border-black rounded p-2">
<span class="material-symbols-outlined">sentiment_satisfied</span>
</span>
Note that the property in the font.material-symbols
section of sites.toml
(the outlined
property in this example) must be set to true
in order to display the smiley icon above.
See Material Symbols for details.
When embedding a component into another component, care should be taken to avoid circular references. If a circular reference is detected, an error message will be inserted in the generated HTML.
To embed a component into a page, article, or layout, add a <tg:component>
element at the
location where you want to place it and specify its name in the name
attribute.
<p>
<tg:component name="smile"></tg:component>
How are you?
</p>
You can pass custom properties to a component using the <tg:component>
element's data-*
attribute.
src/components/avatar.html
<div class="bg-gray-200 p-2">
<a href="articles/members/${name}.html">
<img class="w-24 h-24 rounded-full" src="/images/${name}.png" alt="${name}">
</a>
</div>
src/pages/index.html
<div class="grid grid-rows-2 gap-4">
<tg:component name="avatar" data-name="Alice"></tg:component>
<tg:component name="avatar" data-name="Bob"></tg:component>
<tg:component name="avatar" data-name="Carol"></tg:component>
</div>
Like layouts and segments, slots can be placed inside components. The method of embedding content in the slots within a component is similar to that of a layout and segment.
src/components/blog_item.html
<h3 class="font-bold text-lg m-2">
<tg:slot name="title"></tg:slot>
</h3>
<div>
<tg:slot name="body"></tg:slot>
</div>
<tg:if-complete>
<div class="text-right">
<tg:slot name="date"></tg:slot>
</div>
</tg:if-complete>
src/pages/hello.html
<main class="bg-gray-100 py-2">
<tg:component name="blog_item">
<tg:insert name="title">Greeting</tg:insert>
<tg:insert name="body">
<p>Hello.</p>
</tg:insert>
<tg:insert name="date">2022-12-31</tg:insert>
</tg:component>
</main>
The <tg:prop>
and <tg:data>
elements allow you to embed the value of a property in a component.
---
[data]
message = "Hi!"
---
<span>
<span class="material-symbols-outlined">sentiment_satisfied</span>
<tg:data name="message"></tg:data>
</span>
Note that the component inherits the property from the page or article in which it is embedded. When a property with the same name is defined in a component and a page or article, the value defined in the page or article takes precedence.
Components that are shared among multiple websites are called shared components.
Shared components are placed in the src/shared_components
subdirectory of the working directory.
Usually, this subdirectory is a symbolic link to another directory.
If you initialized your website working directory in the way described in
Managing Multiple Websites,
the src/shared_components
subdirectory will created as a symbolic link referring to the
../../../shared_components
directory.
The way to create shared components is the same as for components. Slots are also available for shared components.
To embed a shared component in a page, article, layout, or component,
add a <tg:shared-component>
element at the desired location and
specify its name in the name
attribute.
It is possible to embed another shared component within a shared component, but it is not possible to embed a normal component within a shared component.
<p>
<tg:shared-component name="smile"></tg:shared-component>
How are you?
</p>
Like a page, an article is a template file used to generate an HTML document.
Articles are placed in the src/articles
subdirectory under the working directory.
It is possible to create a subdirectory under the src/articles
directory and place articles
under it.
The articles will be converted into complete HTML documents and written to the dist/articles
directory.
For example, src/articles/tech.html
is converted to dist/articles/tech.html
and
src/articles/blog/happy_new_year.html
is converted to dist/articles/blog/happy_new_year.html
.
Articles and pages have exactly the same characteristics in the following respects:
- A layout can be applied to them.
- Images and audios can be embedded in them.
- The content of the
<head>
element is automatically generated in the manner described below.
Here we describe how to create an article and display it in a browser.
Create src/articles/glossary.html
with the following contents.
---
[main]
title = "Glossary"
---
<h3>Glossary</h3>
<p>HTML: HyperText Markup Language</p>
<p>CSS: Cascading Style Sheets</p>
dist/articles/glossary.html
generates a complete HTML document including the following <html>
and <head>
elements.
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<h1>Glossary</h1>
<p>HTML: HyperText Markup Language</p>
<p>CSS: Cascading Style Sheets</p>
</body>
</html>
You can specify http://localhost:3000/articles/glossary.html
as the URL to display it in your browser.
Like components, articles can be embedded in pages or segments. It can also be embedded in a page wrapper.
Place <tg:article>
elements where you want to embed articles as follows:
---
[main]
layout = "home"
---
<main>
<h1>Our Recent Articles</h1>
<tg:article name="blog/got_a_gadget"></tg:article>
<tg:article name="blog/happy_new_year"></tg:article>
</main>
The value of the name
attribute of the <tg:article>
element must be the name of the
article file without the extension (.html
).
Unlike components, articles can only be embedded into a page. Articles cannot be embedded in other articles or layouts.
Like layouts and segments, slots can be placed inside articles.
You can also pass custom properties to an article using the <tg:article>
element's data-*
attribute.
When the value of the embedded-only
property of an article is set to true
, it is not converted
into a full HTML file, but is used only for embedding in a page or segment:
---
[main]
embedded-only = true
---
<h3>Greeting</h3>
<p>Hello, world!</p>
The <tg:if-embedded>
element is used within an article's template and is rendered only when the
article is embedded within another page or segment.
The <tg:unless-embedded>
element is used within an article's template and is rendered only when
the article is generated as a separate web page.
0
The <tg:articles>
element can be used to embed multiple articles into a page or segment.
---
[main]
layout = "home"
---
<main>
<h1>Our Proposals</h1>
<tg:articles pattern="proposals/*"></tg:articles>
</main>
The above example embeds all articles in the src/articles/proposals
directory under the
<h1>
element.
By default, articles are sorted in ascending (alphabetically) order by filename.
To sort articles in descending order by their filename, set the order-by
attribute of the
<tg:articles>
element to "filename:desc"
:
---
[main]
layout = "home"
---
<main>
<h1>Our Proposals</h1>
<tg:articles pattern="proposals/*" order-by="filename:desc"></tg:articles>
</main>
You can pass custom properties to an article using the <tg:articles>
element's data-*
attribute.
To sort articles by their title, set the order-by
attribute of the <tg:articles>
element
to "title:asc"
or "title:desc"
:
---
[main]
layout = "home"
---
<main>
<h1>Our Proposals</h1>
<tg:articles pattern="proposals/*" order-by="title:asc"></tg:articles>
</main>
To sort articles in an arbitrary order, set the index
property to an integer value for each
article:
---
[main]
index = 123
---
<article>
...
</article>
Then, set the order-by
attribute of the <tg:articles>
element to "index:asc"
or
"index:desc"
:
---
[main]
layout = "home"
---
<main>
<h1>Our Proposals</h1>
<tg:articles pattern="proposals/*" order-by="index:asc"></tg:articles>
</main>
Articles without the index
property are sorted in ascending order by file name after those with the index
property.
The value of the order-by
attribute is a string separated by a single colon.
The left side of the colon must be "title"
, "index"
or "filename"
.
The right side of the colon must be "asc"
or "desc"
, where "asc"
means "ascending order"
and "desc"
means "descending order".
Templates include pages, layouts, wrappers, segments, components, and articles. Segments, components, and articles in it can be embedded in other templates with <tg:component>
,<tg:segment>
,<tg:article>
, and <tg:articles>
elements, respectively.
Here is a rundown of what can be embedded in what.
embedding destination↓ | <tg:component> |
<tg:segment> |
<tg:article> |
<tg:articles> |
---|---|---|---|---|
pages | Y | Y | Y | Y |
page wrappers | Y | Y | Y | Y |
layouts | Y | Y | N | N |
segments | Y | Y | Y | Y |
components | Y | N | N | N |
articles | Y | N | N | N |
article wrappers | Y | N | N | N |
When linking to a page within your website using the <a>
element, specify the absolute path to
the page in its href
attribute:
<nav>
<a href="/articles/goal.html">Our Goal</a>
<a href="/articles/about.html">About Us</a>
</nav>
When your website is published on Teamgenik, the values of the href
attribute of the <a>
elements in it will be converted appropriately.
The <tg:link>
is a special element used to conditionally cause the <a>
element to appear.
Basically, the content of this element is just rendered as it is.
If there is an <a>
element with a href
attribute of "#"
inside the <tg:link>
element, the href
attribute of the <tg:link>
element is set to the value of the href
attribute of that <a>
element.
The following code is rendered as <a href="/articles/goal">Our Goal</a>
:
<tg:link href="/articles/goal.html">
<a href="#">Our Goal</a>
</tg:link>
However, when an article with the path /articles/goal.html
contains the above code, this part is
removed from the generated HTML document.
Zero or one <tg:if-current>
element may be placed inside the <tg:link>
element.
The content of the <tg:if-current>
element is only rendered if the value of the href
attribute of the <tg:link>
element matches the path of the HTML document that is being generated.
The following code is rendered as <span class="font-bold">Our Goal</span>
when it appears in
an article with the path /articles/goal.html
.
<tg:link href="/articles/goal.html">
<a href="#">Our Goal</a>
<tg:if-current>
<span class="font-bold">Our Goal</span>
</tg:if-current>
</tg:link>
The <tg:label>
element can be used to remove duplication from the above code.
<tg:link href="/articles/goal.html" label="Our Goal">
<a href="#"><tg:label></tg:label></a>
<tg:if-current>
<span class="font-bold"><tg:label></tg:label></span>
</tg:if-current>
</tg:link>
This element is replaced by the value specified in the label
attribute of the <tg:link>
element.
src/components/nav.html
<nav>
<tg:link href="/articles/goal.html" label="Our Goal">
<a href="#" class="underline text-blue-500"><tg:label></tg:label></a>
<tg:if-current>
<span class="font-bold"><tg:label></tg:label></span>
</tg:if-current>
</tg:link>
<tg:link href="/articles/about.html" label="About Us">
<a href="#" class="underline text-blue-500"><tg:label></tg:label></a>
<tg:if-current>
<span class="font-bold"><tg:label></tg:label></span>
</tg:if-current>
</tg:link>
</nav>
When a <tg:link>
element has the component
attribute, the content of the component with the
name corresponding to its value becomes the content of the <tg:link>
element.
For example, suppose there is a nav_link
component with the following content
src/components/nav_link.html
<a href="#" class="underline text-blue-500"><tg:label></tg:label></a>
<tg:if-current>
<span class="font-bold"><tg:label></tg:label></span>
</tg:if-current>
In this case, we can construct a <tg:link>
element as follows:
<tg:link component="nav_link" href="/articles/goal.html" label="Our Goal"></tg:link>
The above code is to be interpreted as exactly the same as the following
<tg:link href="/articles/about.html" label="About Us">
<a href="#" class="underline text-blue-500"><tg:label></tg:label></a>
<tg:if-current>
<span class="font-bold"><tg:label></tg:label></span>
</tg:if-current>
</tg:link>
The <tg:links>
element can be used to embed links to articles in any template
(page, layout, component, article).
---
[main]
layout = "home"
---
<main>
<h1>Our Proposals</h1>
<ul>
<tg:links pattern="proposals/*">
<li>
<a href="#">
<tg:prop name="title"></tg:prop>
<tg:if-complete>
<span class="text-sm">
(<tg:slot name="date"></tg:slot>)
</span>
</tg:if-complete>
</a>
</li>
</tg:links>
</ul>
</main>
The <tg:links>
element contains one or more <a>
elements and
zero or more <tg:prop>
, <tg:data>
and <tg:slot>
elements.
The values of href
attribute of <a>
are replaced by the URL of the article.
The <tg:prop>
and <tg:data>
elements are replaced by the value of a property of the article
to be embedded.
The <tg:slot>
elements are replaced by the content of a <tg:insert>
element defined in the
article to be embedded.
Inside the <tg:links>
element, <tg:if-complete>
elements work the same way
as inside components.
That is, a <tg:if-complete>
element will be removed from the output
unless a value or content is provided for all <tg:prop>
, <tg:data>
, and <tg:slot>
elements
within it.
By default, articles are sorted in ascending (alphabetically) order by file name.
To sort articles by their title, set the order-by
attribute of the <tg:links>
element to
"title:asc"
or "title:desc"
:
<tg:links pattern="proposals/*" order-by="title:asc">
<li>
...
</li>
</tg:links>
See Sorting articles by their title on how to write values
to be set in the order-by
attribute.
To sort articles in an arbitrary order, add the main.index
property to each article's front matter:
---
[main]
layout = "home"
index = 123
---
<article>
...
</article>
Then, specify the order-by
attribute of the <tg:links>
element.
<tg:links pattern="proposals/*" order-by="index:asc">
<li>
...
</li>
</tg:links>
Note that the current specification does not allow a <tg:component>
element to be placed within
a <tg:links>
element.
Zero or one <tg:if-current>
element may be placed inside the <tg:links>
element.
The content of the <tg:if-current>
element is only rendered if the path of the article the path
of the HTML document that is being generated.
You may use tags to classify your articles.
To attach tags to an article, specify their names in the tags
property as an array
using [...]
notation:
---
[main]
tags = [ "travel", "europe" ]
---
<article>
...
</article>
To attach a single tag to an article, the value of the tags
property may be specified as a
string:
---
[main]
tags = "anime"
---
<article>
...
</article>
You can use the filter
attribute to filter articles embedded on the page:
---
[main]
layout = "home"
---
<main>
<h1>Articles (tag:travel)</h1>
<tg:articles pattern="blog/*" filter="tag:travel"></tg:articles>
</main>
The value of the filter
attribute is a string separated by a single colon.
In the current specification, the left side of the colon is always "tag"
and
the right side of the colon is a tag name.
You can also filter the list of links to articles using the filter
attribute:
---
[main]
layout = "home"
---
<main>
<h1>Articles (tag:travel)</h1>
<ul>
<tg:links pattern="blog/*" filter="tag:travel">
<li>
<a href="#">
<tg:prop name="title"></tg:prop>
</a>
</li>
</tg:links>
</ul>
</main>
Note that you cannot assign tags to a page.
When a <tg:links>
element has the component
attribute, the content of the component with the
name corresponding to its value becomes the content of the <tg:links>
element.
For example, suppose there is a nav_link
component with the following content
src/components/nav_link.html
<li>
<a href="#">
<tg:prop name="title"></tg:prop>
<tg:if-complete>
<span class="text-sm">
(<tg:slot name="date"></tg:slot>)
</span>
</tg:if-complete>
</a>
</li>
In this case, we can construct a <tg:links>
element as follows:
<tg:links component="nav_link" pattern="proposals/*"></tg:links>
The above code is to be interpreted as exactly the same as the following
<tg:links pattern="proposals/*">
<li>
<a href="#">
<tg:prop name="title"></tg:prop>
<tg:if-complete>
<span class="text-sm">
(<tg:slot name="date"></tg:slot>)
</span>
</tg:if-complete>
</a>
</li>
</tg:links>
Here we'll show you how to introduce dynamic elements like modals and carousels to your website.
Specifying the tg:modal
attribute on an HTML element makes the following attributes available
to descendant elements of that element:
tg:open
tg:close
We call an HTML element with the tg:modal
attribute a modal.
A modal has two states, open
and close
. Its initial state is close
.
There must be one <dialog>
element inside the modal. Initially, this element is not displayed.
When the user clicks on an element with the tg:open
attribute, the <dialog>
element is displayed.
Conversely, if the user clicks on an element with the tg:close
attribute, the <dialog>
element
will disappear.
What follows is an example of a modal:
<div tg:modal>
<div><button type="button" tg:open>Open</button></div>
<dialog class="rounded-2xl bg-white backdrop:bg-gray-800/80 p-4 h-[360px]">
<h2>Greetings</h2>
<p>Hello, world!</p>
<div><button type="button" tg:close>Close</button></div>
</dialog>
</div>
A transparent element of the same size as the viewport, called a "backdrop," is inserted directly
below the <dialog>
element.
The backdrop deactivates all page content except the <dialog>
element.
The class token backdrop:bg-gray-800/80
in the value of the class
attribute of the <dialog>
element applies a translucent dark gray background color to the backdrop.
See Dialog Backdrops
in the Tailwind CSS Documentation for more information.
Specifying the tg:toggler
attribute on an HTML element makes the following attributes available
to descendant elements of that element:
tg:when
tg:toggle
We call an HTML element with the tg:toggler
attribute a toggler.
A toggler has two states, on
and off
. Its initial state is off
.
An element with a tg:when
attribute inside a toggler will only be displayed if its value matches
the state of the toggler.
When the user clicks or taps an element with a tg:toggle
attribute inside the toggler,
the toggler's state is set to the value of the the attribute.
Here is an example toggler:
<div tg:toggler>
<button type="button" tg:toggle="on" tg:when="off">Open</button>
<div tg:when="on">
<button type="button" tg:toggle="off">Close</button>
<p>Hello, world!</p>
</div>
</div>
When viewing this example in a browser, initially only the "Open" button is visible to the user.
When the user clicks or taps this button, the state of the toggler is set to on
, the "Open" button
disappears, and a "Close" button and a "Hello, world" paragraph appear instead.
When the user further clicks or taps the "Close" button, it returns to the initial state.
If the tg:toggle
attribute's value is omitted, the toggler's state is reversed when the user
clicks or taps on the element.
So the above example could be rewritten as:
<div tg:toggler>
<button type="button" tg:toggle tg:when="off">Open</button>
<div tg:when="on">
<button type="button" tg:toggle>Close</button>
<p>Hello, world!</p>
</div>
</div>
Specifying the tg:switcher
attribute on an HTML element makes the following attributes available
to descendant elements of that element:
tg:item
tg:choose
tg:first
tg:prev
tg:next
tg:last
tg:paginator
We call an HTML element with the tg:switcher
attribute a switcher.
Inside the switcher, there must always be an element with the tg:body
attribute, a switcher body.
In addition, there must be elements with the tg:item
attribute, switcher items,
inside the switcher body.
Swticher items are assigned unique index numbers starting from zero in sequence. A switcher has a state represented by an integer value, called an current index number. A switcher item will only be displayed if its index number matches the current index number of the switcher.
When the user clicks or taps an element with a tg:choose
attribute inside the switcher,
the switcher's current index number is set to the value of the the attribute.
Here is an example switcher:
<div tg:switcher>
<div tg:body>
<div tg:item>A</div>
<div tg:item>B</div>
<div tg:item>C</div>
<div tg:item>D</div>
<div tg:item>E</div>
</div>
<nav>
<button type="button" tg:choose="0">a</button>
<button type="button" tg:choose="1">b</button>
<button type="button" tg:choose="2">c</button>
<button type="button" tg:choose="3">d</button>
<button type="button" tg:choose="4">e</button>
</nav>
</div>
If the the value of tg:choose
attribute of a button matches switcher's current index number
nothing happens when the user clicks or taps this button.
To visually indicate this, use the tg:current-class
and tg:normal-class
attributes to
change the style applied to the button.
<button
type="button"
tg:choose="0"
class="btn"
tg:current-class="btn-primary cursor-default"
tg:normal-class="btn-secondary">a</button>
You can create a button that change the state of the switcher using special attributes such as
tg:first
, tg:prev
, tg:next
, tg:last
, etc. instead of the tg:choose
attribute.
<div tg:switcher>
<div tg:body>
<div tg:item>A</div>
<div tg:item>B</div>
<div tg:item>C</div>
<div tg:item>D</div>
<div tg:item>E</div>
</div>
<nav>
<button type="button" tg:first>First</button>
<button type="button" tg:prev>Prev</button>
<button type="button" tg:next>Next</button>
<button type="button" tg:last>Last</button>
</nav>
</div>
If the switcher's current index number matches the its lower bound,
nothing happens when the user clicks or taps a button with tg:first
or tg:prev
attribute.
Similarly, if the switcher's current index number matches the its upper bound,
nothing happens when the user clicks or taps a button with tg:next
or tg:last
attribute.
To visually indicate this, use the tg:enabled-class
and tg:disabled-class
attributes to
change the style applied to the button.
<button
type="button"
tg:first
class="btn"
tg:enabled-class="btn-primary"
tg:disabled-class="btn-disabled">First</button>
If the switcher has an tg:interval
attribute, the switcher's index number is incremented
by 1 at the specified interval (unit: millisecond).
<div tg:switcher tg:interval="5000">
...
</div>
If you want to add a fade-in/fade-out effect to the switcher items, specify the time in milliseconds
required for the fade-in or fade-out effect to complete in the tg:transition-duration
attribute
of the switcher.
<div tg:switcher tg:interval="5000" tg:transition-duration="750">
...
</div>
When the user clicks or taps a button with a tg:choose
attribute, etc., the switcher's state
no longer changes automatically.
Inside the switcher, there may be an element with the tg:paginator
attribute.
This element will be a template for a group of buttons that will allow the user to choose the
a switcher item to be displayed.
We call these buttons pagination buttons.
For example, if the number of switcher items is five, the following code example generates a
<nav>
element with five <button>
elements inside.
<div tg:switcher>
...
<nav>
<button type="button" tg:paginator></button>
</nav>
</div>
This will just create a <button></button>
element, which is invisible to the user, and must be styled with CSS. For example, a round button appears when you do the following.
<nav>
<button
tg:paginator
class="rounded-full w-6 h-6 mx-1 bg-gray-500"
></button>
</nav>
Additionally, the UI can be refined by.
<nav>
<button
type="button"
tg:paginator
class="rounded-full w-6 h-6 mx-1 opacity-50"
tg:normal-class="bg-teal-400 hover:opacity-75"
tg:current-class="bg-orange-400 cursor-default"
></button>
</nav>
We call an HTML element with the tg:rotator
attribute a rotator.
A rotator behaves exactly like a switcher with three reservations:
- If the user clicks a button with the
tg:next
attribute when the current index number matches the upper bound, the index number will be set to its lower bound. - If the user clicks a button with the
tg:prev
attribute when the current index number matches the lower bound, the index number will be set to its upper bound. - When the
tg:interval
attribute is set, when the current index number reaches the upper bound, the next time the index number is set to its lower bound.
Here is an example rotator:
<div tg:rotator tg:interval="5000" tg:transition-duration="750">
<div tg:body>
<div tg:item>A</div>
<div tg:item>B</div>
<div tg:item>C</div>
<div tg:item>D</div>
<div tg:item>E</div>
</div>
<nav>
<button type="button" tg:prev>Prev</button>
<button type="button" tg:next>Next</button>
</nav>
</div>
We call an HTML element with the tg:carousel
attribute a carousel.
This allows website authors to display multiple pieces of content sequentially in a slideshow-like format.
Inside the carousel, there must always be an element with the tg:frame
attribute, a carousel frame.
Also, inside the carousel frame, there must be an element with the tg:body
attribute, a carousel body.
In addition, there must be elements with the tg:item
attribute, carousel items,
inside the carousel body.
The width of the carousel body is automatically calculated to be an integer multiple of the width
of the first carousel item, so there is no need for the website author to specify it.
Its width becomes large enough to allow all carousel items to be aligned horizontally, but only a
portion of it will be visible to website visitors because of the overflow: hidden
style of the
carousel frame.
The carousel effect is achieved by shifting the carousel body left and right with an embedded
JavaScript program.
The width of the carousel frame should be adjusted by the website author. Normally, match the width of the carousel frame and the first carousel item. That way, only one carousel item will be displayed in the carousel frame while the carousel remains still. If you want to always display multiple carousel items, make the width of the carousel frame larger than the width of the first carousel item.
---
[style]
carousel-frame = "w-[240px] h-[180px]"
carousel-body = """
[&>div] { w-[240px] h-[180px] object-cover }
[&>div>img] { w-full h-full object-cover object-center}
"""
---
<div tg:carousel>
<div tg:frame tg:class="carousel-frame">
<div tg:body tg:class="carousel-body">
<div tg:item><img src="/images/slides/a.png"></div>
<div tg:item><img src="/images/slides/b.png"></div>
<div tg:item><img src="/images/slides/c.png"></div>
<div tg:item><img src="/images/slides/d.png"></div>
<div tg:item><img src="/images/slides/e.png"></div>
</div>
</div>
</div>
As with rotators, placing an element with a tg:prev
or tg:next
attribute inside the carousel
allows the user to control the state of the carousel.
<nav>
<button type="button" tg:prev>Prev</button>
<button type="button" tg:next>Next</button>
</nav>
To automatically rotate the carousel items at regular time intervals, specify a positive integer
for the tg:interval
attribute of the carousel.
The value specified in this attribute is interpreted as time in milliseconds.
<div tg:carousel tg:interval="3000">
If you want to add animation effect to the rotation of the carousel, specify the
tg:transition-duration
attribute of the carousel.
<div tg:carousel tg:interval="3000" tg:transition-duration="500">
By default, the horizontal movement of carousel items is linear, i.e., they move at the even speed.
If you want to fine-tune the way they move, specify a class with a name beginning with "ease-" for the carousel body.
<div tg:carousel tg:transition-duration="1000">
<div tg:frame tg:class="carousel-frame">
<div tg:body class="ease-in-out" tg:class="carousel-body">
...
</div>
</div>
</div>
The ease-in-out
class moderates the movement near the beginning and near the end of the change.
See Transition Timing Function for
details.
If the tg:transition-duration
attribute is set on the carousel, a user clicking/tapping the
"prev" or "next" button while the carousel body is shifting horizontally will have no effect.
To visually indicate this, specify the tg:enabled-class
and tg:disabled-class
attributes to
the buttons.
Class tokens specified in the tg:enabled-class
attribute are added to the class
attribute of
the button when the carousel body is stopped, and class tokens specified in the tg:disabled-class
attribute are added to the class
attribute of the button when the carousel body is shifting.
<button
type="button"
tg:prev
class="rounded-full w-12 h-12 opacity-50"
tg:enabled-class="bg-teal-400 hover:opacity-75"
tg:disabled-class="bg-gray-400 cursor-default"
>
<span class="material-symbols-outlined">arrow_back</span>
</button>
Inside the carousel, there may be an element with the tg:paginator
attribute.
This element will be a template for a group of buttons that will allow the user to choose the
a carousel item to be displayed in the center of the carousel frame.
We call these buttons pagination buttons.
For example, if the number of carousel items is five, the following code example generates a
<nav>
element with five <button>
elements inside.
<div tg:carousel>
...
<nav>
<button type="button" tg:paginator></button>
</nav>
</div>
Each pagination button corresponds to one of the carousel items.
If desired, you may code individual pagination buttons by specifying the number of the carousel
item in the tg:choose
attribute.
The following example generates a <nav>
element with five <button>
elements, as in the
previous example.
<nav>
<button type="button" tg:choose="0"></button>
<button type="button" tg:choose="1"></button>
<button type="button" tg:choose="2"></button>
<button type="button" tg:choose="3"></button>
<button type="button" tg:choose="4"></button>
</nav>
Note that each carousel item is numbered starting with zero.
If you want to give a prominent style to the pagination button corresponding to the carousel item
displayed in the center of the carousel frame, use the tg:normal-class
and tg:current-class
attributes.
<nav>
<button
type="button"
tg:paginator
class="rounded-full w-6 h-6 mx-1 opacity-50"
tg:normal-class="bg-teal-400 hover:opacity-75"
tg:current-class="bg-orange-400 cursor-default"
>
</button>
</nav>
Class tokens specified in the tg:normal-class
attribute are applied to buttons corresponding to
carousel items that are not currently displayed in the center of the carousel frame,
and class tokens specified in the tg:current-class
attribute are applied to the button
corresponding to the carousel item that is currently displayed in the center of the carousel frame.
When the tg:transition-duration
attribute is set on the carousel, a user clicking/tapping the pagination
buttons while the carousel body is shifting horizontally will have no effect.
To visually indicate this, specify the tg:disabled-class
attributes to
the buttons.
<nav>
<button
type="button"
tg:paginator
class="rounded-full w-6 h-6 mx-1 opacity-50"
tg:normal-class="bg-teal-400 hover:opacity-75"
tg:current-class="bg-orange-400 cursor-default"
tg:disabled-class="bg-gray-400 cursor-default"
>
</button>
</nav>
Class tokens specified in the tg:disabled-class
attribute are added to the class
attribute of
all pagination buttons when the carousel body is shifting.
Scheduler is a mechanism for changing the class
attribute of an HTML element and its
descendant elements over time.
The following is a simple example of scheduler configuration:
<div
tg:scheduler
class="w-24 mx-auto p-4 text-center text-white"
tg:init="bg-black"
tg:1000="bg-red-500"
tg:2000="bg-blue-500"
tg:3000="bg-green-500"
>
Hello, world!
</div>
The tg:scheduler
attribute declares that this element is managed by a scheduler.
The value of this element's class
attribute is called the base class.
The base class plus the value of the tg:init
attribute is initially the class
attribute of
this element.
In this example, the class
attribute of the <div>
element is initially set to the value
"w-24 mx-auto p-4 text-center text-white bg-black"
.
The attribute named tg:
combined with a sequence of numbers changes the class
attribute of
this element at the moment a certain amount of time has elapsed since the web page was loaded.
The sequence of numbers represents the elapsed time in milliseconds.
In this example, the value of the class
attribute of the <div>
element changes over time as
follows:
- After 1 second:
w-24 mx-auto p-4 text-center text-white bg-red-500
- After 2 seconds:
w-24 mx-auto p-4 text-center text-white bg-blue-500
- After 3 seconds:
w-24 mx-auto p-4 text-center text-white bg-green-500
In the following example, the scheduler is used to achieve the fade-in effect:
<div
tg:scheduler
tg:init="opacity-0"
tg:0="opacity-100 transition duration-500"
>
Fade In
</div>
Initially, the value of the class
attribute of this <div>
element contains opacity-0
, so its
contents are not visible to the user.
The moment the page loads, opacity-0
is removed from the class
attribute of this <div>
element and opacity-100
is added instead.
Thanks to the transition duration-500
included in the base class, the effect of opacity-100
is
applied gradually over a period of 0.5 seconds.
Tram is a mechanism for changing the class
attribute of an HTML element and its descendant
elements as the positional relationship between the element and the viewport changes.
When a user scrolls a web page on which a tram is placed from top to bottom, the tram progresses from bottom to top. When the head of the tram touches the bottom of the viewport, we say tram progress is 0. When the rear of the tram touches the upper edge of the viewport, we say tram progress is 100.
The following is a simple example of tram configuration:
<div tg:tram>
<div
class="w-48 h-48 mx-auto"
tg:init="bg-black"
tg:forward-50="bg-red-500"
>
</div>
</div>
This tram has one inner <div>
element. The inner element has the tg:forward-50
attribute, and
the presence of this attribute makes this element the target of the tram.
The value of the target's class
attribute is called the base class.
Initially, the actual value of the class attribute is the base class plus bg-black
specified in
the tg:init
attribute.
Then, by specifying the attribute tg:forward-50
, the moment tram progress reaches 50, the base
class plus bg-red-500
is set as the target's class
attribute.
Tram progresses, represented by a number such as 50, are called trigger points.
Attributes whose names begin with tg:forward-
are called forward triggers, and attributes
whose names begin with tg:backward-
are called backward triggers.
Class tokens specified with a forward trigger are added to the target's class
attribute when
the tram reaches the trigger point of that trigger while moving forward.
Class tokens specified with a backward trigger are added to the target's class
attribute when
the tram reaches the trigger point of that trigger while moving backward.
Setting multiple triggers to a single target is allowed. In the following example, the background color changes from black to red and then from red to green as the tram progresses.
<div tg:tram>
<div
class="w-48 h-48 mx-auto"
tg:init="bg-black"
tg:forward-25="bg-red-500"
tg:forward-50="bg-green-500"
>
</div>
</div>
If you want the color change to be gradual, add transition
and duration-1000
to the base class:
<div tg:tram>
<div
class="w-48 h-48 mx-auto transition duration-1000"
tg:init="bg-black"
tg:forward-50="bg-red-500"
>
</div>
</div>
This way, when tram advances to the center of the viewport, the background color will switch from black to red over 1000 ms.
When a user scrolls a web page from bottom to top, the tram moves backward from the top of the screen to the bottom.
If you want the target's class
attribute to change while tram is moving backward, specify
the backward triggers:
<div tg:tram>
<div
class="w-48 h-48 mx-auto"
tg:init="bg-black"
tg:forward-50="bg-red-500"
tg:backward-50="bg-black"
>
</div>
</div>
In the example above, the target's background color changes from red to black at the moment the tram reaches center of the viewport while moving backward.
So far we have used bare integers from 0 to 100 to represent trigger points, but by adding a unit to integers, we can represent a variety of trigger points.
100%
represents a trigger point equivalent to a progress equal to the length (height) of the tram.
For example, class tokens set to the tg:forward-50%
attribute will be added to the target's class
attribute when the tram advances its half the length from the bottom of the viewport.
In the following example, the target in the tram is initially outside the left edge of the viewport, and when the tram advances until its tail touches the bottom edge of the viewport, it takes 1000ms to return to its original position.
<div tg:tram class="overflow-hidden">
<div
class="w-48 h-48 mx-auto bg-black transition duration-1000"
tg:init="translate-x-[-100vw]"
tg:forward-100%="translate-x-0"
>
</div>
</div>
100vh
represents a trigger point equivalent to a progress equal to the height of the viewport.
For example, class tokens set to the tg:forward-50vh
attribute will be added to the target's class
attribute when the head of the tram is at the same height as the midpoint of the viewport.
100px
represents the trigger point which corresponds to 100 pixels of progress.
For example, the tg:forward-64px
attribute has as its value the class tokens that should be
applied when the tram advances 64 pixels beyond the bottom edge of the viewport.
It is possible to add an additional suffix, +
or -
, to these units.
The suffix +
means that tram progress is measured relative to the top of the viewport.
For example, 50%+
indicates that the tram has advanced from the top of the viewport by half its
own length.
The suffix -
means that the tram progress reference is the top edge of the viewport and the
trigger point is backward away from the top edge of the viewport.
For example, 64px-
indicates the head of the tram is 64 pixels behind the top edge of the
viewport.
The tgweb uses Alpine.js to achieve dynamic content manipulation.
However, website authors themselves cannot use attributes derived from Alpine.js such as x-data
, @click
, and :class
.
When those attributes are found in the source HTML files, they will be removed.
Teamgenik has an aspect of being a no-code development platform. Applications created on Teamgenik are called "mini-apps". In the STUDIO space on Teamgenik you can create mini-apps, and in the MARKET space you can acquire or purchase mini-apps.
Mini-pps have two uses:
- You can use them as stand-alone widgets in your BASE space.
- You can embed them on your personal or your team's website.
Note that you can only embed the mini-apps on websites that are published on Teamgenik.
Therefore, it is not possible to run mini-apps on top of the web pages delivered by the web server
started by the npx tgweb-server
command.
However, it is possible to embed placeholders for mini-apps in them.
You can then upload the website data to Teamgenik with the npx tgweb-push
command and publish
the website with the embedded mini-apps.
Note The npx tgweb-push
command is not yet available.
To embed mini-app placeholders in your web page, you must specify the default locale of mini-apps
and register their name, ID, and display name in site.toml
.
Shown below is an example of the setup.
default-locale = "en"
[[apps]]
name = "score_board"
id = "121d0e34-2398-4f7d-a8be-bdc549cd4332"
display-name = "Score Board"
[[apps]]
name = "players_list"
id = "70955db7-75f6-43b2-b46a-50413e43b94f"
display-name = "Players List"
When the texts in mini-apps are internationalized, they are translated in the locale set in the
default-locale
property.
You must place [[apps]]
on the first line of each mini-app configuration.
Note that the word "apps" is enclosed in double square brackets.
The name
property is the name used to identify the mini-app to be configured.
Its value can be any string, but it must correspond to the name
attribute of the <tg:app>
tag
described below.
The id
property is the identifier (ID) assigned to the mini-app on Teamgenik.
Its value has the form of a UUID (Universally Unique IDentifier).
You can check the ID of each mini app on the "Mini apps" tab in the Teamgenik BASE space.
This property is optional.
The ID is necessary to actually run a mini-app on the website published on Teamgenik, but if you simply want to embed a its placeholder in a web page in the local environment and check its appearance, you can omit it.
The value of display-name
property is a string that will be displayed within the placeholder.
If this property is omitted, the value of the name
property is used instead.
Use the <tg:app>
element to embed mini-app placeholders into your web page.
Its value of the name
attribute must match the name
property of one of the mini-apps described
in site.toml
.
The following is an example of the use the <tg:app>
element:
<tg:app name="score_board"></tg:app>
The <tg:app>
element can have an expanded
attribute. This attribute controls the display mode
of mini-apps.
Mini apps have two display modes: standard mode and expanded mode. The layout of the mini-apps in standard mode is optimized for a width of 300 pixels, while the mini-apps in expanded mode are optimized for a width of 640 pixels.
The expanded
attribute is a boolean attribute; if the attribute is present, the mini-app is
displayed in expanded mode; otherwise, it is displayed in standard mode.
The following is an example of the use the expanded
attribute:
<tg:app name="score_board" expanded></tg:app>
Note that the mini-app placeholder itself does not have a specific width and height.
Usually, you should fix the width of the placeholder by enclosing it in a <div>
element like this:
<div class="w-[300px]">
<tg:app name="score_board"></tg:app>
</div>
Specifying a minimum height as shown below may prevent the web page layout from wobbling when the content of the mini-app changes.
<div class="w-[300px] min-h-[450px]">
<tg:app name="score_board"></tg:app>
</div>
To switch the display mode depending on whether the display width of the browser is 640 pixels or
more, place two placeholders as follows, surround each with a <div>
element which has
an appropriate set of Tailwind CSS class tokens.
<div class="w-[300px] sm:hidden">
<tg:app name="score_board"></tg:app>
</div>
<div class="w-[640px] hidden sm:block">
<tg:app name="score_board" expanded></tg:app>
</div>
The sm:hidden
class hides elements when the browser display width is greater than 640 pixels.
The hidden sm:block
class hides the element when the browser display width is less than 640 pixels.
To learn more about the Tailwind CSS class tokens mentioned here, please visit the following pages:
- https://tailwindcss.com/docs/width#arbitrary-values
- https://tailwindcss.com/docs/min-height#arbitrary-values
- https://tailwindcss.com/docs/responsive-design
- https://tailwindcss.com/docs/display
Creating a file named site.toml
in the src
directory allows you to set values for properties
at the site level. The values set here will be the default values for properties set in the
front matter of each page.
src/site.toml
title = "No title"
layout = "common"
[data]
current-year = 2023
You can embed the URL of an image or audio file into the content
attribute of a <meta>
element using %{...}
notation:
[data]
icon-url = "%{images/icons/default.png}"
theme-url = "%{audios/our_theme.mp3}"
See elements for specific examples of its use.
If no value is set for a particular property in the front matter of a page, tgweb will search for a value in the following order:
- the front matter of its wrapper if available
- the front matter of its layout if available
sites.toml
if available
For example, suppose that the value "a"
is set to the custom property x
in the front
matter of a page as follows:
[data]
x = "a"
And suppose the front matter of its wrapper has the following settings:
[data]
x = "b"
y = "c"
In this case, the value of the custom property x
for this page is "a"
and the value of the
custom property y
is "c"
.
In addition, suppose that the front matter of the layout applied to this page has the following settings:
[data]
z = "d"
If so, the value of property z
on this page is "d"
.
And suppose that sites.toml
is configured as follows:
[data]
s = "e"
In this case, the value of the custom property s
for this page is "e"
.
Custom properties as well as predefined properties such as title
are inherited as well.
Also, properties that belong to "meta", "http-equiv", "meta-property", and "link" sections are
inherited. However, properties that belong to "style" section are not inherited.
When an article is rendered as an independent HTML document, the property inheritance mechanism is exactly the same as that of a page.
When an article is embedded in a page, segment, or layout, it inherits properties from the wrapper
surrounding it, if any, and site.toml
but not from the page, segment, or layout that embeds that
article.
When a wrapper or layout is applied to a page or article, the values that are substituted for
the <tg:prop>
and <tg:data>
elements in that wrapper or layout are the values of
properties that the page or article has.
For example, suppose a page has the following front matter
[data]
x = "a"
And suppose its wrapper has the following front matter and HTML fragment:
---
[data]
x = "b"
y = "c"
---
<header>
<tg:data name="x"></tg:data>
<tg:data name="y"></tg:data>
</header>
<tg:content></tg:content>
Then, the text content of the <header>
element of the HTML document generated from them will
be "a c" instead of "b c".
When a segment is embedded into a page, the values that are substituted for
the <tg:prop>
and <tg:data>
elements in that segment are the values of
properties that the page has.
Similarly, when a segment is into a layout, the values that are substituted for
the <tg:prop>
and <tg:data>
elements in that segment are the values of
properties of the main template (page or article).
When a component is embedded into a page, article, segment, wrapper, layout,
the values that are substituted for the <tg:prop>
and <tg:data>
elements in that template
are the values of properties of the main template (page or article).
The content of the <title>
element is determined from the (page or article) template
by the following rules:
- The value of the
title
property if available - The text content of the first
<h1>
element if available - The text content of the first
<h2>
element if available - The text content of the first
<h3>
element if available - The text content of the first
<h4>
element if available - The text content of the first
<h5>
element if available - The text content of the first
<h6>
element if available "No Title"
Note that even if the value of the title
property is not set in the front matter of the page or article, it may inherit from the wrapper, layout, or sites.toml
.
The title of the HTML document generated from the template below will be "Greeting":
---
[main]
title = "Greeting"
---
<body>
<h1>Welcome!</h1>
<div class="bg-green-300 p-4">
<p>Hello, world!</p>
</div>
</body>
The string "Welcome!" is extracted as the title from the following template:
<body>
<h1>Welcome!</h1>
<div class="bg-green-300 p-4">
<p>Hello, world!</p>
</div>
</body>
If the next template is rendered as an HTML document, its title will be "No Title":
<body>
<div class="text-2xl">Welcome!</div>
<div class="bg-green-300 p-4">
<p>Hello, world!</p>
</div>
</body>
The <meta>
elements in the <head>
element are generated by the values of properties that
belongs to "meta", "http-equiv", or "meta-property" sections.
Note that the <head>
element of the generated HTML document always contains a
<meta charset="utf-8">
element.
You can generate a <meta>
element with a name
attribute by setting the value to a property
that belongs to the "meta.name" section:
[meta.name]
viewport = "width=device-width, initial-scale=1"
theme-color = "#2da0a8"
description = "Description"
robots = "index,follow"
generator = "Teamgenik"
Setting the values of the properties as above will produce the following <meta>
elements:
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#2da0a8">
<meta name="description" content="Description">
<meta name="robots" content="index,follow">
<meta name="generator" content="Teamgenik">
If you want to generate multiple <meta>
elements with the same name, write as follows:
[meta.name]
googlebot = [ "index,follow", "notranslate" ]
The above will generate the following <meta>
elements
<meta name="googlebot" content="index,follow">
<meta name="googlebot" content="notranslate">
You can generate a <meta>
element with a http-equiv
attribute by setting the value to a
property that belongs to "http-equiv" section.
[meta.http-equiv]
content-security-policy = "default-src 'self'"
x-dns-prefetch-control = "off"
The above settings will generate the following <meta>
elements:
<meta http-equiv="content-security-policy" content="default-src 'self'">
<meta http-equiv="x-dns-prefetch-control" content="off">
Teamgenik converts these paths into URLs appropriately.
[meta.property]
"fb:app_id" = "1234567890abcde"
"fb:article_style" = "default"
"fb:use_automatic_ad_placement" = "true"
"op:markup_version" = "v1.0"
"al:ios:app_name" = "App Links"
"al:android:app_name" = "App Links"
The above settings will generate the following <meta>
elements
<meta property="fb:app_id" content="1234567890abcde">
<meta property="fb:article_style" content="default">
<meta property="fb:use_automatic_ad_placement" content="true">
<meta property="op:markup_version" content="v1.0">
<meta property="al:ios:app_name" content="App Links">
<meta property="al:android:app_name" content="App Links">
You can embed the value of a property into the content
attribute of a <meta>
element using
${...}
notation:
[meta.property]
"og:url" = "${url}"
"og:title" = "${title}"
"og:description" = "${meta.name.description}"
To refer to the value of a property belonging to the "meta" section, add meta.
before the property
name.
You can embed the URL of an image or audio file into the content
attribute of a <meta>
element using %{...}
notation:
[meta.property]
"og:image" = "%{/images/icon.png}"
"og:audio" = "%{/audios/theme.mp3}"
The <link>
elements in the <head>
element are generated by the values of properties that
belong to "link" section, "links" section or "stylesheets" section.
[link]
canonical = "https://example.com/"
license = "%{/copyright.html}"
[[links]]
blocking = "render"
href = "example.woff2"
as = "font"
[[links]]
rel = "preload"
href = "my_font.woff2"
as = "font"
type = "font/woff2"
crossorigin = "anonymous"
[[stylesheets]]
main = true
secondary = false
"foo/bar" = true
The above will generate the following <link>
elements
<link rel="canonical" href="https://example.com/">
<link rel="license" href="http://localhost:3000/copyright.html">
<link blocking="render" href="example.woff2" as="font">
<link rel="preload" href="myFont.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="/main.css">
<link rel="stylesheet" type="text/css" href="/foo/bar.css">
When your website is published on Teamgenik, URLs generated from %{...}
notation will be
converted appropriately.
The following <link>
element are always inserted within the head element.
<link href="/css/tailwind.css" rel="stylesheet">
A <link>
element that refers to another stylesheet cannot be inserted within the head element.
If you import a .zip
file created with the npx tgweb-archive
command into TeamGenik,
the "stylesheets" section in site.toml
and frontmatter will be ignored.
This means that you cannot use your own CSS files on websites managed and published on TeamGenik.
The <script>
elements within the <head>
element are generated by the values of properties
that belong to the "scripts" section.
The following <script>
elements are always inserted within the head element.
<script src="/js/alpine.min.js" defer></script>
<script src="/reload/reload.js" defer></script>
If you import a .zip
file created with the npx tgweb-archive
command into TeamGenik,
the "scripts" section in site.toml
and frontmatter will be ignored.
This means that you cannot use your own JavaScript files in websites that you manage and publish on TeamGenik.
<tg:animation>
: Animations<tg:article>
: Embedding an article in a page or segment<tg:articles>
: Embedding articles in a page<tg:app>
: Embedding Teamgenik Mini-apps<tg:component>
: Embedding components<tg:content>
: Adding a layout, Adding a wrapper<tg:data>
: Custom properties<tg:if-complete>
:<tg:if-complete>
<tg:if-current>
:<tg:link>, <tg:if-current> and <tg:label>
<tg:if-embedded>
:<tg:if-embedded> and <tg:unless-embedded>
<tg:insert>
: Slots and inserts<tg:label>
:<tg:link>, <tg:if-current> and <tg:label>
<tg:link>
:<tg:link>, <tg:if-current> and <tg:label>
<tg:links>
: Link list<tg:prop>
: Embedding predefined property values in a template<tg:segment>
: Embedding segments into a page<tg:shared-component>
: Shared components<tg:slot>
: Slots and inserts<tg:unless-embedded>
:<tg:if-embedded> and <tg:unless-embedded>
tg:1000
, etc.: Schedulertg:backward-*
: Tramtg:body
: Switcher, Rotator, Carouseltg:carousel
: Carouseltg:choose
: Switcher, Rotatortg:class
: Defining style aliasestg:close
: Modaltg:current-class
: Switcher, Rotator, Carouseltg:disabled-class
: Carouseltg:enabled-class
: Carouseltg:first
: Switcher, Rotatortg:frame
: Carouseltg:forward-*
: Tramtg:init
: Scheduler, Tramtg:interval
: Rotator, Carouseltg:item
: Carouseltg:last
: Switcher, Rotatortg:modal
: Modaltg:next
: Switcher, Rotatortg:normal-class
: Switcher, Rotator, Carouseltg:open
: Modaltg:paginator
: Switcher/Paginator, Carousel/Paginatortg:prev
: Switcher, Rotatortg:rotator
: Rotatortg:switcher
: Switchertg:scheduler
: Schedulertg:toggle
: Togglertg:toggler
: Togglertg:tram
: Tramtg:transition-duration
: Carouseltg:when
: Toggler
See TODO.md.
tgweb is MIT licensed.